@clawhub-omesh06-e774946a82
Atlassian Jira expert for creating and managing projects, planning, product discovery, JQL queries, workflows, custom fields, automation, reporting, and all...
--- name: "jira-expert" description: Atlassian Jira expert for creating and managing projects, planning, product discovery, JQL queries, workflows, custom fields, automation, reporting, and all Jira features. Use for Jira project setup, configuration, advanced search, dashboard creation, workflow design, and technical Jira operations. --- # Atlassian Jira Expert Master-level expertise in Jira configuration, project management, JQL, workflows, automation, and reporting. Handles all technical and operational aspects of Jira. ## Quick Start — Most Common Operations **Create a project**: ``` mcp jira create_project --name "My Project" --key "MYPROJ" --type scrum --lead "[email protected]" ``` **Run a JQL query**: ``` mcp jira search_issues --jql "project = MYPROJ AND status != Done AND dueDate < now()" --maxResults 50 ``` For full command reference, see [Atlassian MCP Integration](#atlassian-mcp-integration). For JQL functions, see [JQL Functions Reference](#jql-functions-reference). For report templates, see [Reporting Templates](#reporting-templates). --- ## Workflows ### Project Creation 1. Determine project type (Scrum, Kanban, Bug Tracking, etc.) 2. Create project with appropriate template 3. Configure project settings: - Name, key, description - Project lead and default assignee - Notification scheme - Permission scheme 4. Set up issue types and workflows 5. Configure custom fields if needed 6. Create initial board/backlog view 7. **HANDOFF TO**: Scrum Master for team onboarding ### Workflow Design 1. Map out process states (To Do → In Progress → Done) 2. Define transitions and conditions 3. Add validators, post-functions, and conditions 4. Configure workflow scheme 5. **Validate**: Deploy to a test project first; verify all transitions, conditions, and post-functions behave as expected before associating with production projects 6. Associate workflow with project 7. Test workflow with sample issues ### JQL Query Building **Basic Structure**: `field operator value` **Common Operators**: - `=, !=` : equals, not equals - `~, !~` : contains, not contains - `>, <, >=, <=` : comparison - `in, not in` : list membership - `is empty, is not empty` - `was, was in, was not` - `changed` **Powerful JQL Examples**: Find overdue issues: ```jql dueDate < now() AND status != Done ``` Sprint burndown issues: ```jql sprint = 23 AND status changed TO "Done" DURING (startOfSprint(), endOfSprint()) ``` Find stale issues: ```jql updated < -30d AND status != Done ``` Cross-project epic tracking: ```jql "Epic Link" = PROJ-123 ORDER BY rank ``` Velocity calculation: ```jql sprint in closedSprints() AND resolution = Done ``` Team capacity: ```jql assignee in (user1, user2) AND sprint in openSprints() ``` ### Dashboard Creation 1. Create new dashboard (personal or shared) 2. Add relevant gadgets: - Filter Results (JQL-based) - Sprint Burndown - Velocity Chart - Created vs Resolved - Pie Chart (status distribution) 3. Arrange layout for readability 4. Configure automatic refresh 5. Share with appropriate teams 6. **HANDOFF TO**: Senior PM or Scrum Master for use ### Automation Rules 1. Define trigger (issue created, field changed, scheduled) 2. Add conditions (if applicable) 3. Define actions: - Update field - Send notification - Create subtask - Transition issue - Post comment 4. Test automation with sample data 5. Enable and monitor ## Advanced Features ### Custom Fields **When to Create**: - Track data not in standard fields - Capture process-specific information - Enable advanced reporting **Field Types**: Text, Numeric, Date, Select (single/multi/cascading), User picker **Configuration**: 1. Create custom field 2. Configure field context (which projects/issue types) 3. Add to appropriate screens 4. Update search templates if needed ### Issue Linking **Link Types**: - Blocks / Is blocked by - Relates to - Duplicates / Is duplicated by - Clones / Is cloned by - Epic-Story relationship **Best Practices**: - Use Epic linking for feature grouping - Use blocking links to show dependencies - Document link reasons in comments ### Permissions & Security **Permission Schemes**: - Browse Projects - Create/Edit/Delete Issues - Administer Projects - Manage Sprints **Security Levels**: - Define confidential issue visibility - Control access to sensitive data - Audit security changes ### Bulk Operations **Bulk Change**: 1. Use JQL to find target issues 2. Select bulk change operation 3. Choose fields to update 4. **Validate**: Preview all changes before executing; confirm the JQL filter matches only intended issues — bulk edits are difficult to reverse 5. Execute and confirm 6. Monitor background task **Bulk Transitions**: - Move multiple issues through workflow - Useful for sprint cleanup - Requires appropriate permissions - **Validate**: Run the JQL filter and review results in small batches before applying at scale ## JQL Functions Reference > **Tip**: Save frequently used queries as named filters instead of re-running complex JQL ad hoc. See [Best Practices](#best-practices) for performance guidance. **Date**: `startOfDay()`, `endOfDay()`, `startOfWeek()`, `endOfWeek()`, `startOfMonth()`, `endOfMonth()`, `startOfYear()`, `endOfYear()` **Sprint**: `openSprints()`, `closedSprints()`, `futureSprints()` **User**: `currentUser()`, `membersOf("group")` **Advanced**: `issueHistory()`, `linkedIssues()`, `issuesWithFixVersions()` ## Reporting Templates > **Tip**: These JQL snippets can be saved as shared filters or wired directly into Dashboard gadgets (see [Dashboard Creation](#dashboard-creation)). | Report | JQL | |---|---| | Sprint Report | `project = PROJ AND sprint = 23` | | Team Velocity | `assignee in (team) AND sprint in closedSprints() AND resolution = Done` | | Bug Trend | `type = Bug AND created >= -30d` | | Blocker Analysis | `priority = Blocker AND status != Done` | ## Decision Framework **When to Escalate to Atlassian Admin**: - Need new project permission scheme - Require custom workflow scheme across org - User provisioning or deprovisioning - License or billing questions - System-wide configuration changes **When to Collaborate with Scrum Master**: - Sprint board configuration - Backlog prioritization views - Team-specific filters - Sprint reporting needs **When to Collaborate with Senior PM**: - Portfolio-level reporting - Cross-project dashboards - Executive visibility needs - Multi-project dependencies ## Handoff Protocols **FROM Senior PM**: - Project structure requirements - Workflow and field needs - Reporting requirements - Integration needs **TO Senior PM**: - Cross-project metrics - Issue trends and patterns - Workflow bottlenecks - Data quality insights **FROM Scrum Master**: - Sprint board configuration requests - Workflow optimization needs - Backlog filtering requirements - Velocity tracking setup **TO Scrum Master**: - Configured sprint boards - Velocity reports - Burndown charts - Team capacity views ## Best Practices **Data Quality**: - Enforce required fields with field validation rules - Use consistent issue key naming conventions per project type - Schedule regular cleanup of stale/orphaned issues **Performance**: - Avoid leading wildcards in JQL (`~` on large text fields is expensive) - Use saved filters instead of re-running complex JQL ad hoc - Limit dashboard gadgets to reduce page load time - Archive completed projects rather than deleting to preserve history **Governance**: - Document rationale for custom workflow states and transitions - Version-control permission/workflow schemes before making changes - Require change management review for org-wide scheme updates - Run permission audits after user role changes ## Atlassian MCP Integration **Primary Tool**: Jira MCP Server **Key Operations with Example Commands**: Create a project: ``` mcp jira create_project --name "My Project" --key "MYPROJ" --type scrum --lead "[email protected]" ``` Execute a JQL query: ``` mcp jira search_issues --jql "project = MYPROJ AND status != Done AND dueDate < now()" --maxResults 50 ``` Update an issue field: ``` mcp jira update_issue --issue "MYPROJ-42" --field "status" --value "In Progress" ``` Create a sprint: ``` mcp jira create_sprint --board 10 --name "Sprint 5" --startDate "2024-06-01" --endDate "2024-06-14" ``` Create a board filter: ``` mcp jira create_filter --name "Open Blockers" --jql "priority = Blocker AND status != Done" --shareWith "project-team" ``` **Integration Points**: - Pull metrics for Senior PM reporting - Configure sprint boards for Scrum Master - Create documentation pages for Confluence Expert - Support template creation for Template Creator ## Related Skills - **Confluence Expert** (`project-management/confluence-expert/`) — Documentation complements Jira workflows - **Atlassian Admin** (`project-management/atlassian-admin/`) — Permission and user management for Jira projects FILE:references/automation-examples.md # Jira Automation Examples ## Auto-Assignment Rules ### Auto-assign by component **Trigger:** Issue created **Conditions:** - Component is not EMPTY **Actions:** - Assign issue to component lead ### Auto-assign to reporter for feedback **Trigger:** Issue transitioned to "Waiting for Feedback" **Actions:** - Assign issue to reporter - Add comment: "Please provide additional information" ### Round-robin assignment **Trigger:** Issue created **Conditions:** - Project = ABC - Assignee is EMPTY **Actions:** - Assign to next team member in rotation (use smart value) --- ## Status Sync Rules ### Sync subtask status to parent **Trigger:** Issue transitioned **Conditions:** - Issue type = Sub-task - Transition is to "Done" - Parent issue exists - All subtasks are Done **Actions:** - Transition parent issue to "Done" ### Sync parent to subtasks **Trigger:** Issue transitioned **Conditions:** - Issue type has subtasks - Transition is to "Cancelled" **Actions:** - For each: Sub-tasks - Transition issue to "Cancelled" ### Epic progress tracking **Trigger:** Issue transitioned **Conditions:** - Epic link is not EMPTY - Transition is to "Done" **Actions:** - Add comment to epic: "{{issue.key}} completed" - Update epic custom field "Progress" --- ## Notification Rules ### Slack notification for high-priority bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority IN (Highest, High) **Actions:** - Send Slack message to #engineering: ``` 🚨 High Priority Bug Created {{issue.key}}: {{issue.summary}} Reporter: {{issue.reporter.displayName}} Priority: {{issue.priority.name}} {{issue.url}} ``` ### Email assignee when mentioned **Trigger:** Issue commented **Conditions:** - Comment contains @mention of assignee **Actions:** - Send email to {{issue.assignee.emailAddress}}: ``` Subject: You were mentioned in {{issue.key}} Body: {{comment.author.displayName}} mentioned you: {{comment.body}} ``` ### SLA breach warning **Trigger:** Scheduled - Every hour **Conditions:** - Status != Done - SLA time remaining < 2 hours **Actions:** - Send email to {{issue.assignee}} - Add comment: "⚠️ SLA expires in <2 hours" - Set priority to Highest --- ## Field Automation Rules ### Auto-set due date **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority = Highest **Actions:** - Set due date to {{now.plusDays(1)}} ### Clear assignee when in backlog **Trigger:** Issue transitioned **Conditions:** - Transition is to "Backlog" - Assignee is not EMPTY **Actions:** - Assign issue to Unassigned - Add comment: "Returned to backlog, assignee cleared" ### Auto-populate sprint field **Trigger:** Issue transitioned **Conditions:** - Transition is to "In Progress" - Sprint is EMPTY **Actions:** - Add issue to current sprint ### Set fix version based on component **Trigger:** Issue created **Conditions:** - Component = "Mobile App" **Actions:** - Set fix version to "Mobile v2.0" --- ## Escalation Rules ### Auto-escalate stale issues **Trigger:** Scheduled - Daily at 9:00 AM **Conditions:** - Status = "Waiting for Response" - Updated < -7 days **Actions:** - Add comment: "@{{issue.reporter}} This issue needs attention" - Send email to project lead - Add label: "needs-attention" ### Escalate overdue critical issues **Trigger:** Scheduled - Every hour **Conditions:** - Priority IN (Highest, High) - Due date < now() - Status != Done **Actions:** - Transition to "Escalated" - Assign to project manager - Send Slack notification ### Auto-close inactive issues **Trigger:** Scheduled - Daily at 10:00 AM **Conditions:** - Status = "Waiting for Customer" - Updated < -30 days **Actions:** - Transition to "Closed" - Add comment: "Auto-closed due to inactivity" - Send email to reporter --- ## Sprint Automation Rules ### Move incomplete work to next sprint **Trigger:** Sprint closed **Conditions:** - Issue status != Done **Actions:** - Add issue to next sprint - Add comment: "Moved from {{sprint.name}}" ### Auto-remove completed items from active sprint **Trigger:** Issue transitioned **Conditions:** - Transition is to "Done" - Sprint IN openSprints() **Actions:** - Remove issue from sprint - Add comment: "Removed from active sprint (completed)" ### Sprint start notification **Trigger:** Sprint started **Actions:** - Send Slack message to #team: ``` 🚀 Sprint {{sprint.name}} Started! Goal: {{sprint.goal}} Committed: {{sprint.issuesCount}} issues ``` --- ## Approval Workflow Rules ### Request approval for large stories **Trigger:** Issue created **Conditions:** - Issue type = Story - Story points >= 13 **Actions:** - Transition to "Pending Approval" - Assign to product owner - Send email notification ### Auto-approve small bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority IN (Low, Lowest) **Actions:** - Transition to "Approved" - Add comment: "Auto-approved (low-priority bug)" ### Require security review **Trigger:** Issue transitioned **Conditions:** - Transition is to "Ready for Release" - Labels contains "security" **Actions:** - Transition to "Security Review" - Assign to security-team - Send email to [email protected] --- ## Integration Rules ### Create GitHub issue **Trigger:** Issue transitioned **Conditions:** - Transition is to "In Progress" - Labels contains "needs-tracking" **Actions:** - Send webhook to GitHub API: ```json { "title": "{{issue.key}}: {{issue.summary}}", "body": "{{issue.description}}", "assignee": "{{issue.assignee.name}}" } ``` ### Update Confluence page **Trigger:** Issue transitioned **Conditions:** - Issue type = Epic - Transition is to "Done" **Actions:** - Send webhook to Confluence: - Update epic status page - Add completion date --- ## Quality & Testing Rules ### Require test cases for features **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Transition is to "Ready for QA" - Custom field "Test Cases" is EMPTY **Actions:** - Transition back to "In Progress" - Add comment: "❌ Test cases required before QA" ### Auto-create test issue **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Transition is to "Ready for QA" **Actions:** - Create linked issue: - Type: Test - Summary: "Test: {{issue.summary}}" - Link type: "tested by" - Assignee: QA team ### Flag regression bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Affects version is in released versions **Actions:** - Add label: "regression" - Set priority to High - Add comment: "🚨 Regression in released version" --- ## Documentation Rules ### Require documentation for features **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Labels contains "customer-facing" - Transition is to "Done" - Custom field "Documentation Link" is EMPTY **Actions:** - Reopen issue - Add comment: "📝 Documentation required for customer-facing feature" ### Auto-create doc task **Trigger:** Issue transitioned **Conditions:** - Issue type = Epic - Transition is to "In Progress" **Actions:** - Create subtask: - Type: Task - Summary: "Documentation for {{issue.summary}}" - Assignee: {{issue.assignee}} --- ## Time Tracking Rules ### Log work reminder **Trigger:** Issue transitioned **Conditions:** - Transition is to "Done" - Time spent is EMPTY **Actions:** - Add comment: "⏱️ Reminder: Please log your time" ### Warn on high time spent **Trigger:** Work logged **Conditions:** - Time spent > original estimate * 1.5 **Actions:** - Add comment: "⚠️ Time spent exceeds estimate by 50%" - Send notification to assignee and project manager --- ## Advanced Conditional Rules ### Conditional assignee based on priority **Trigger:** Issue created **Conditions:** - Issue type = Bug **Actions:** - If: Priority = Highest - Assign to on-call engineer - Else if: Priority = High - Assign to team lead - Else: - Assign to next available team member ### Multi-step approval flow **Trigger:** Issue transitioned **Conditions:** - Transition is to "Request Approval" - Budget estimate > $10,000 **Actions:** - If: Budget > $50,000 - Assign to CFO - Send email to executive team - Else if: Budget > $10,000 - Assign to Director - Add comment: "Director approval required" - Add label: "pending-approval" --- ## Smart Value Examples ### Dynamic assignee based on component ``` {{issue.components.first.lead.accountId}} ``` ### Days since created ``` {{issue.created.diff(now).days}} ``` ### Conditional message ``` {{#if(issue.priority.name == "Highest")}} 🚨 CRITICAL {{else}} ℹ️ Normal priority {{/}} ``` ### List all subtasks ``` {{#issue.subtasks}} - {{key}}: {{summary}} ({{status.name}}) {{/}} ``` ### Calculate completion percentage ``` {{issue.subtasks.filter(item => item.status.statusCategory.key == "done").size.divide(issue.subtasks.size).multiply(100).round()}}% ``` --- ## Best Practices 1. **Test in sandbox** - Always test rules on test project first 2. **Start simple** - Begin with basic rules, add complexity incrementally 3. **Use conditions wisely** - Narrow scope to reduce unintended triggers 4. **Monitor audit log** - Check automation execution history regularly 5. **Limit actions** - Keep rules focused, don't chain too many actions 6. **Name clearly** - Use descriptive names: "Auto-assign bugs to component lead" 7. **Document rules** - Add description explaining purpose and owner 8. **Review regularly** - Audit rules quarterly, disable unused ones 9. **Handle errors** - Add error handling for webhooks and integrations 10. **Performance** - Avoid scheduled rules that query large datasets hourly FILE:references/AUTOMATION.md # Jira Automation Reference Comprehensive guide to Jira automation rules: triggers, conditions, actions, smart values, and production-ready recipes. ## Rule Structure Every automation rule follows this pattern: ``` TRIGGER → [CONDITION(s)] → ACTION(s) ``` - **Trigger**: The event that starts the rule (required, exactly one) - **Condition**: Filters to narrow when the rule fires (optional, multiple allowed) - **Action**: What the rule does (required, one or more) ## Triggers ### Issue Triggers | Trigger | Fires When | Use For | |---------|------------|---------| | **Issue created** | New issue is created | Auto-assignment, notifications, SLA start | | **Issue transitioned** | Status changes | Workflow automation, notifications | | **Issue updated** | Any field changes | Field sync, cascading updates | | **Issue commented** | Comment is added | Auto-responses, SLA tracking | | **Issue assigned** | Assignee changes | Workload notifications | | **Issue linked** | Link is added/removed | Dependency tracking | | **Issue deleted** | Issue is deleted | Cleanup, audit logging | ### Sprint & Board Triggers | Trigger | Fires When | |---------|------------| | **Sprint started** | Sprint is activated | | **Sprint completed** | Sprint is closed | | **Issue moved between sprints** | Issue is moved | | **Backlog item moved to sprint** | Item is pulled into sprint | ### Scheduled Triggers | Trigger | Fires When | |---------|------------| | **Scheduled** | Cron-based (daily, weekly, custom) | | **Issue stale** | No updates for X days | ### Version Triggers | Trigger | Fires When | |---------|------------| | **Version created** | New version added | | **Version released** | Version is released | ## Conditions ### Issue Conditions | Condition | Matches When | |-----------|-------------| | **Issue fields condition** | Field matches value (e.g., priority = High) | | **JQL condition** | Issue matches JQL query | | **Related issues condition** | Linked/sub-task issues match criteria | | **User condition** | Actor matches (reporter, assignee, group) | | **Advanced compare** | Complex field comparisons | ### Condition Operators ``` Field = value # Exact match Field != value # Not equal Field > value # Greater than (numeric/date) Field is empty # Field has no value Field is not empty # Field has a value Field changed # Field was modified in this event Field changed to # Field changed to specific value Field changed from # Field changed from specific value ``` ## Actions ### Issue Actions | Action | Does | |--------|------| | **Edit issue** | Update any field on the current issue | | **Transition issue** | Move to a new status | | **Assign issue** | Change assignee | | **Comment on issue** | Add a comment | | **Create issue** | Create a new linked issue | | **Create sub-tasks** | Create child issues | | **Clone issue** | Duplicate the issue | | **Delete issue** | Remove the issue | | **Link issues** | Add issue links | | **Log work** | Add time tracking entry | ### Notification Actions | Action | Does | |--------|------| | **Send email** | Send custom email to users/groups | | **Send Slack message** | Post to Slack channel (requires integration) | | **Send Microsoft Teams message** | Post to Teams (requires integration) | | **Send web request** | HTTP call to external service | ### Lookup & Branch Actions | Action | Does | |--------|------| | **Lookup issues (JQL)** | Find issues matching JQL, iterate over them | | **Create branch** | Branch logic (if/then/else) | | **For each** | Loop over found issues | ## Smart Values Smart values are dynamic placeholders that resolve at runtime. ### Issue Smart Values ``` {{issue.key}} # PROJ-123 {{issue.summary}} # Issue title {{issue.description}} # Full description {{issue.status.name}} # Current status {{issue.priority.name}} # Priority level {{issue.assignee.displayName}} # Assignee name {{issue.reporter.displayName}} # Reporter name {{issue.issuetype.name}} # Issue type {{issue.project.key}} # Project key {{issue.created}} # Creation date {{issue.updated}} # Last update date {{issue.fixVersions}} # Fix versions {{issue.labels}} # Labels array {{issue.components}} # Components array ``` ### Transition Smart Values ``` {{transition.from_status}} # Previous status {{transition.to_status}} # New status {{transition.transitionName}} # Transition name ``` ### User Smart Values ``` {{initiator.displayName}} # Who triggered the rule {{initiator.emailAddress}} # Their email {{initiator.accountId}} # Their account ID ``` ### Date Smart Values ``` {{now}} # Current timestamp {{now.plusDays(7)}} # 7 days from now {{now.minusHours(24)}} # 24 hours ago {{issue.created.plusBusinessDays(3)}} # 3 business days after creation ``` ### Conditional Smart Values ``` {{#if issue.priority.name == "High"}} This is high priority {{/if}} {{#if issue.assignee}} Assigned to {{issue.assignee.displayName}} {{else}} Unassigned {{/if}} ``` ## Production-Ready Recipes ### 1. Auto-Assign by Component ```yaml Trigger: Issue created Condition: Issue has component Action: Edit issue - Assignee = Component lead Rule Logic: IF component = "Backend" → assign to @backend-lead IF component = "Frontend" → assign to @frontend-lead IF component = "DevOps" → assign to @devops-lead ``` ### 2. SLA Warning — Stale Issues ```yaml Trigger: Scheduled (daily at 9am) Condition: JQL = "status != Done AND updated <= -5d AND priority in (High, Highest)" Action: - Add comment: "⚠️ This {{issue.priority.name}} issue hasn't been updated in 5+ days." - Send Slack: "#engineering-alerts: {{issue.key}} is stale ({{issue.assignee.displayName}})" ``` ### 3. Auto-Close Resolved Issues After 7 Days ```yaml Trigger: Scheduled (daily) Condition: JQL = "status = Resolved AND updated <= -7d" Action: - Transition: Resolved → Closed - Comment: "Auto-closed after 7 days in Resolved status." ``` ### 4. Sprint Spillover Notification ```yaml Trigger: Sprint completed Condition: Issue status != Done Action: - Comment: "Spilled over from Sprint {{sprint.name}}. Reason needs review." - Add label: "spillover" - Send email to: {{issue.assignee.emailAddress}} ``` ### 5. Sub-Task Completion → Parent Transition ```yaml Trigger: Issue transitioned (to Done) Condition: Issue is sub-task AND all sibling sub-tasks are Done Action (on parent): - Transition: In Progress → In Review - Comment: "All sub-tasks completed. Ready for review." ``` ### 6. Bug Priority Escalation ```yaml Trigger: Scheduled (every 4 hours) Condition: JQL = "type = Bug AND priority = High AND status = Open AND created <= -24h" Action: - Edit: priority = Highest - Comment: "⚡ Auto-escalated: High-priority bug open for 24+ hours." - Send email to: project lead ``` ### 7. Auto-Link Duplicate Detection ```yaml Trigger: Issue created Condition: JQL finds issues with similar summary (fuzzy) Action: - Comment: "Possible duplicate of {{lookupIssues.first.key}}: {{lookupIssues.first.summary}}" - Add label: "possible-duplicate" ``` ### 8. Release Notes Generator ```yaml Trigger: Version released Condition: None Action: - Lookup: JQL = "fixVersion = {{version.name}} AND status = Done" - Create Confluence page: Title: "Release Notes — {{version.name}}" Content: List of resolved issues with types and summaries ``` ### 9. Workload Balancer — Round-Robin Assignment ```yaml Trigger: Issue created Condition: Issue type = Story AND assignee is empty Action: - Lookup: JQL = "assignee in (dev1, dev2, dev3) AND sprint in openSprints() AND status != Done" - Assign to team member with fewest open issues ``` ### 10. Blocker Notification Chain ```yaml Trigger: Issue updated (priority changed to Blocker) Action: - Send email to: project lead, scrum master - Send Slack: "#blockers: 🚨 {{issue.key}} marked as Blocker by {{initiator.displayName}}" - Comment: "Blocker escalated. Notified: PM + SM." - Edit: Add label "blocker-active" ``` ## Best Practices 1. **Name rules descriptively** — "Auto-assign Backend bugs to @dev-lead" not "Rule 1" 2. **Add conditions before actions** — prevent unintended execution 3. **Use JQL conditions** for precision — field conditions can miss edge cases 4. **Test in a sandbox project first** — automation mistakes can be destructive 5. **Set rate limits** — avoid infinite loops (Rule A triggers Rule B triggers Rule A) 6. **Monitor rule execution** — check Automation audit log weekly 7. **Document business rules** — explain WHY the rule exists, not just WHAT it does 8. **Use branches (if/else)** over separate rules — reduces rule count, easier to maintain 9. **Disable before deleting** — observe for a week to ensure no side effects 10. **Version your automation** — export rules as JSON backup before major changes FILE:references/jql-examples.md # JQL Query Examples ## Sprint Queries **Current sprint issues:** ```jql sprint IN openSprints() ORDER BY rank ``` **Issues in specific sprint:** ```jql sprint = "Sprint 23" ORDER BY priority DESC ``` **All sprint work (current and backlog):** ```jql project = ABC AND issuetype IN (Story, Bug, Task) ORDER BY sprint DESC, rank ``` **Unscheduled stories:** ```jql project = ABC AND issuetype = Story AND sprint IS EMPTY AND status != Done ORDER BY priority DESC ``` **Spillover from last sprint:** ```jql sprint IN closedSprints() AND sprint NOT IN (latestReleasedVersion()) AND status != Done ORDER BY created DESC ``` **Sprint completion rate:** ```jql sprint = "Sprint 23" AND status = Done ``` ## User & Team Queries **My open issues:** ```jql assignee = currentUser() AND status != Done ORDER BY priority DESC, created ASC ``` **Unassigned in my project:** ```jql project = ABC AND assignee IS EMPTY AND status != Done ORDER BY priority DESC ``` **Issues I'm watching:** ```jql watcher = currentUser() AND status != Done ``` **Team workload:** ```jql assignee IN membersOf("engineering-team") AND status IN ("In Progress", "In Review") ORDER BY assignee, priority DESC ``` **Issues I reported that are still open:** ```jql reporter = currentUser() AND status != Done ORDER BY created DESC ``` **Issues commented on by me:** ```jql comment ~ currentUser() AND status != Done ``` ## Date Range Queries **Created today:** ```jql created >= startOfDay() ORDER BY created DESC ``` **Updated in last 7 days:** ```jql updated >= -7d ORDER BY updated DESC ``` **Created this week:** ```jql created >= startOfWeek() AND created <= endOfWeek() ``` **Created this month:** ```jql created >= startOfMonth() AND created <= endOfMonth() ``` **Not updated in 30 days:** ```jql status != Done AND updated <= -30d ORDER BY updated ASC ``` **Resolved yesterday:** ```jql resolved >= startOfDay(-1d) AND resolved < startOfDay() ``` **Due this week:** ```jql duedate >= startOfWeek() AND duedate <= endOfWeek() AND status != Done ``` **Overdue:** ```jql duedate < now() AND status != Done ORDER BY duedate ASC ``` ## Status & Workflow Queries **In Progress issues:** ```jql project = ABC AND status = "In Progress" ORDER BY assignee ``` **Blocked issues:** ```jql project = ABC AND labels = blocked AND status != Done ``` **Issues in review:** ```jql project = ABC AND status IN ("Code Review", "QA Review", "Pending Approval") ORDER BY updated ASC ``` **Ready for development:** ```jql project = ABC AND status = "Ready" AND sprint IS EMPTY ORDER BY priority DESC ``` **Recently done:** ```jql project = ABC AND status = Done AND resolved >= -7d ORDER BY resolved DESC ``` **Status changed today:** ```jql status CHANGED AFTER startOfDay() ORDER BY updated DESC ``` **Long-running in progress:** ```jql status = "In Progress" AND status CHANGED BEFORE -14d ORDER BY status CHANGED ASC ``` ## Priority & Type Queries **High priority bugs:** ```jql issuetype = Bug AND priority IN (Highest, High) AND status != Done ORDER BY priority DESC, created ASC ``` **Critical blockers:** ```jql priority = Highest AND status != Done ORDER BY created ASC ``` **All epics:** ```jql issuetype = Epic ORDER BY status, priority DESC ``` **Stories without acceptance criteria:** ```jql issuetype = Story AND "Acceptance Criteria" IS EMPTY AND status = Backlog ``` **Technical debt:** ```jql labels = tech-debt AND status != Done ORDER BY priority DESC ``` ## Complex Multi-Condition Queries **My team's sprint work:** ```jql sprint IN openSprints() AND assignee IN membersOf("engineering-team") AND status != Done ORDER BY assignee, priority DESC ``` **Bugs created this month, not in sprint:** ```jql issuetype = Bug AND created >= startOfMonth() AND sprint IS EMPTY AND status != Done ORDER BY priority DESC, created DESC ``` **High-priority work needing attention:** ```jql project = ABC AND priority IN (Highest, High) AND status IN ("In Progress", "In Review") AND updated <= -3d ORDER BY priority DESC, updated ASC ``` **Stale issues:** ```jql project = ABC AND status NOT IN (Done, Cancelled) AND (assignee IS EMPTY OR updated <= -30d) ORDER BY created ASC ``` **Epic progress:** ```jql "Epic Link" = ABC-123 ORDER BY status, rank ``` ## Component & Version Queries **Issues in component:** ```jql project = ABC AND component = "Frontend" AND status != Done ``` **Issues without component:** ```jql project = ABC AND component IS EMPTY AND status != Done ``` **Target version:** ```jql fixVersion = "v2.0" ORDER BY status, priority DESC ``` **Released versions:** ```jql fixVersion IN releasedVersions() ORDER BY fixVersion DESC ``` ## Label & Text Search Queries **Issues with label:** ```jql labels = urgent AND status != Done ``` **Multiple labels (AND):** ```jql labels IN (frontend, bug) AND status != Done ``` **Search in summary:** ```jql summary ~ "authentication" ORDER BY created DESC ``` **Search in summary and description:** ```jql text ~ "API integration" ORDER BY created DESC ``` **Issues with empty description:** ```jql description IS EMPTY AND issuetype = Story ``` ## Performance-Optimized Queries **Good - Specific project first:** ```jql project = ABC AND status = "In Progress" AND assignee = currentUser() ``` **Bad - User filter first:** ```jql assignee = currentUser() AND status = "In Progress" AND project = ABC ``` **Good - Use functions:** ```jql sprint IN openSprints() AND status != Done ``` **Bad - Hardcoded sprint:** ```jql sprint = "Sprint 23" AND status != Done ``` **Good - Specific date:** ```jql created >= 2024-01-01 AND created <= 2024-01-31 ``` **Bad - Relative with high cost:** ```jql created >= -365d AND created <= -335d ``` ## Reporting Queries **Velocity calculation:** ```jql sprint = "Sprint 23" AND status = Done ``` *Then sum story points* **Bug rate:** ```jql project = ABC AND issuetype = Bug AND created >= startOfMonth() ``` **Average cycle time:** ```jql project = ABC AND resolved >= startOfMonth() AND resolved <= endOfMonth() ``` *Calculate time from In Progress to Done* **Stories delivered this quarter:** ```jql project = ABC AND issuetype = Story AND resolved >= startOfYear() AND resolved <= endOfQuarter() ``` **Team capacity:** ```jql assignee IN membersOf("engineering-team") AND sprint IN openSprints() ``` *Sum original estimates* ## Notification & Watching Queries **Issues I need to review:** ```jql status = "Pending Review" AND assignee = currentUser() ``` **Issues assigned to me, high priority:** ```jql assignee = currentUser() AND priority IN (Highest, High) AND status != Done ``` **Issues created by me, not resolved:** ```jql reporter = currentUser() AND status != Done ORDER BY created DESC ``` ## Advanced Functions **Issues changed from status:** ```jql status WAS "In Progress" AND status = "Done" AND status CHANGED AFTER startOfWeek() ``` **Assignee changed:** ```jql assignee CHANGED BY currentUser() AFTER -7d ``` **Issues re-opened:** ```jql status WAS Done AND status != Done ORDER BY updated DESC ``` **Linked issues:** ```jql issue IN linkedIssues("ABC-123") ORDER BY issuetype ``` **Parent epic:** ```jql parent = ABC-123 ORDER BY rank ``` ## Saved Filter Examples **Daily Standup Filter:** ```jql assignee = currentUser() AND sprint IN openSprints() AND status != Done ORDER BY priority DESC ``` **Team Sprint Board Filter:** ```jql project = ABC AND sprint IN openSprints() ORDER BY rank ``` **Bugs Dashboard Filter:** ```jql project = ABC AND issuetype = Bug AND status != Done ORDER BY priority DESC, created ASC ``` **Tech Debt Backlog:** ```jql project = ABC AND labels = tech-debt AND status = Backlog ORDER BY priority DESC ``` **Needs Triage:** ```jql project = ABC AND status = "To Triage" AND created >= -7d ORDER BY created ASC ``` FILE:references/WORKFLOWS.md # Jira Workflows Reference Comprehensive guide to Jira workflow design, transitions, conditions, validators, and post-functions. ## Default Workflows ### Simplified Workflow ``` Open → In Progress → Done ``` ### Software Development Workflow ``` Backlog → Selected for Development → In Progress → In Review → Done ↑___________________________| (reopen) ``` ### Bug Tracking Workflow ``` Open → In Progress → Fixed → Verified → Closed ↑ | | |____Reopened________|________| ``` ## Custom Workflow Design ### Design Principles 1. **Mirror your actual process** — don't force teams into artificial states 2. **Minimize statuses** — each status must represent a distinct work state where the item waits for a different action 3. **Clear ownership** — every status should have an obvious responsible party 4. **Allow rework** — always provide paths back for rejected/reopened items 5. **Separate "waiting" from "working"** — distinguish "In Review" (waiting) from "Reviewing" (actively working) ### Status Categories Jira maps every status to one of four categories that drive board columns and JQL: | Category | Meaning | JQL | Examples | |----------|---------|-----|----------| | `To Do` | Not started | `statusCategory = "To Do"` | Backlog, Open, New | | `In Progress` | Active work | `statusCategory = "In Progress"` | In Progress, In Review, Testing | | `Done` | Completed | `statusCategory = Done` | Done, Closed, Released | | `Undefined` | Legacy/unused | — | Avoid using | ### Recommended Statuses by Team Type **Engineering Team:** ``` Backlog → Ready → In Progress → Code Review → QA → Done ``` **Support Team:** ``` New → Triaged → In Progress → Waiting on Customer → Resolved → Closed ``` **Design Team:** ``` Backlog → Research → Design → Review → Approved → Handoff ``` ## Transitions ### Transition Properties | Property | Description | |----------|-------------| | **Name** | Display name on the button (e.g., "Start Work") | | **Screen** | Form shown during transition (optional) | | **Conditions** | Who can trigger this transition | | **Validators** | Rules that must pass before transition executes | | **Post-functions** | Actions executed after transition completes | ### Common Transition Patterns **Start Work:** ``` Trigger: "Start Work" button Condition: Assignee only Validator: Issue must have assignee Post-function: Set "In Progress" resolution to None ``` **Submit for Review:** ``` Trigger: "Submit for Review" button Condition: Assignee or project admin Validator: All sub-tasks must be Done Post-function: Add comment "Submitted for review by {user}" ``` **Approve:** ``` Trigger: "Approve" button Condition: Must be in "Reviewers" group Validator: Must add comment Post-function: Set resolution to "Done", fire event ``` ## Conditions ### Built-in Conditions | Condition | Use When | |-----------|----------| | **Only Assignee** | Only assigned user can transition | | **Only Reporter** | Only creator can transition | | **Permission Condition** | User must have specific permission | | **Group Condition** | User must be in specified group | | **Sub-Task Blocking** | All sub-tasks must be resolved | | **Previous Status** | Issue must have been in a specific status | | **User Is In Role** | User must have project role (Developer, Admin) | ### Combining Conditions - **AND logic**: Add multiple conditions to one transition — ALL must pass - **OR logic**: Create parallel transitions with different conditions ## Validators ### Built-in Validators | Validator | Checks | |-----------|--------| | **Required Field** | Specific field must be populated | | **Field Has Been Modified** | Field must change during transition | | **Regular Expression** | Field must match regex pattern | | **Permission Validator** | User must have permission | | **Previous Status Validator** | Issue was in a required status | ### Common Validator Patterns ``` # Require comment on rejection Validator: Comment Required When: Transition = "Reject" # Require fix version before release Validator: Required Field = "Fix Version/s" When: Transition = "Release" # Require time logged before closing Validator: Field Required = "Time Spent" (must be > 0) When: Transition = "Close" ``` ## Post-Functions ### Built-in Post-Functions | Post-Function | Action | |---------------|--------| | **Set Field Value** | Assign a value to any field | | **Update Issue Field** | Change assignee, priority, etc. | | **Create Comment** | Add automated comment | | **Fire Event** | Trigger notification event | | **Assign to Lead** | Assign to project lead | | **Assign to Reporter** | Assign back to creator | | **Clear Field** | Remove field value | | **Copy Value** | Copy field from parent/linked issue | ### Post-Function Execution Order Post-functions execute in defined order. Standard sequence: 1. Set issue status (automatic, always first) 2. Add comment (if configured) 3. Update fields 4. Generate change history (automatic, always last) 5. Fire event (triggers notifications) **Important:** "Generate change history" and "Fire event" must always be last — reorder if you add custom post-functions. ## Workflow Schemes ### What They Do - Map issue types to workflows within a project - One workflow scheme per project - Different issue types can use different workflows ### Configuration Pattern ``` Project: MYPROJ Workflow Scheme: "Engineering Workflow Scheme" Bug → Bug Tracking Workflow Story → Development Workflow Task → Simple Workflow Epic → Epic Workflow Sub-task → Sub-task Workflow (inherits parent transitions) ``` ## Best Practices 1. **Start simple, add complexity only when needed** — a 5-status workflow beats a 15-status one 2. **Name transitions as actions** — "Start Work" not "In Progress" (the status is "In Progress", the action is "Start Work") 3. **Use screens sparingly** — only show a screen when you need data from the user during transition 4. **Test with real users** — workflows that look good on paper may confuse the team 5. **Document your workflow** — add descriptions to statuses and transitions 6. **Use global transitions carefully** — a "Cancel" transition from any status is convenient but can bypass important gates 7. **Audit quarterly** — remove statuses with <5% usage FILE:scripts/jql_query_builder.py #!/usr/bin/env python3 """ JQL Query Builder Pattern-matching JQL builder from natural language descriptions. Maps common phrases to JQL operators and constructs valid queries with syntax validation. Usage: python jql_query_builder.py "high priority bugs in PROJECT assigned to me" python jql_query_builder.py "overdue tasks in PROJ" --format json python jql_query_builder.py --patterns """ import argparse import json import re import sys from datetime import datetime from typing import Any, Dict, List, Optional, Tuple # --------------------------------------------------------------------------- # Pattern Library # --------------------------------------------------------------------------- PATTERN_LIBRARY = { "my_open_bugs": { "phrases": ["my open bugs", "my bugs", "bugs assigned to me"], "jql": 'assignee = currentUser() AND type = Bug AND status != Done', "description": "All open bugs assigned to current user", }, "high_priority_bugs": { "phrases": ["high priority bugs", "critical bugs", "urgent bugs", "p1 bugs"], "jql": 'type = Bug AND priority in (Highest, High) AND status != Done', "description": "High and highest priority open bugs", }, "my_open_tasks": { "phrases": ["my open tasks", "my tasks", "tasks assigned to me", "my work"], "jql": 'assignee = currentUser() AND status != Done', "description": "All open issues assigned to current user", }, "unassigned_issues": { "phrases": ["unassigned", "unassigned issues", "no assignee"], "jql": 'assignee is EMPTY AND status != Done', "description": "Issues with no assignee", }, "recently_created": { "phrases": ["recently created", "new issues", "created this week", "recent"], "jql": 'created >= -7d ORDER BY created DESC', "description": "Issues created in the last 7 days", }, "recently_updated": { "phrases": ["recently updated", "updated this week", "recent changes"], "jql": 'updated >= -7d ORDER BY updated DESC', "description": "Issues updated in the last 7 days", }, "overdue": { "phrases": ["overdue", "past due", "missed deadline", "overdue tasks"], "jql": 'duedate < now() AND status != Done', "description": "Issues past their due date", }, "due_this_week": { "phrases": ["due this week", "due soon", "upcoming deadlines"], "jql": 'duedate >= startOfWeek() AND duedate <= endOfWeek() AND status != Done', "description": "Issues due this week", }, "blocked_issues": { "phrases": ["blocked", "blocked issues", "impediments"], "jql": 'status = Blocked OR status = Impediment', "description": "Issues in blocked or impediment status", }, "in_progress": { "phrases": ["in progress", "being worked on", "active work"], "jql": 'status = "In Progress"', "description": "Issues currently in progress", }, "sprint_issues": { "phrases": ["current sprint", "this sprint", "active sprint"], "jql": 'sprint in openSprints()', "description": "Issues in the current active sprint", }, "backlog": { "phrases": ["backlog", "backlog items", "not started"], "jql": 'sprint is EMPTY AND status = "To Do" ORDER BY priority DESC', "description": "Issues in the backlog not assigned to a sprint", }, "stories_without_estimates": { "phrases": ["no estimates", "unestimated", "missing estimates", "no story points"], "jql": 'type = Story AND (storyPoints is EMPTY OR storyPoints = 0) AND status != Done', "description": "Stories missing story point estimates", }, "epics_in_progress": { "phrases": ["active epics", "epics in progress", "open epics"], "jql": 'type = Epic AND status != Done ORDER BY priority DESC', "description": "Epics that are not yet completed", }, "done_this_week": { "phrases": ["done this week", "completed this week", "resolved this week"], "jql": 'status changed to Done DURING (startOfWeek(), now())', "description": "Issues completed during the current week", }, "created_vs_resolved": { "phrases": ["created vs resolved", "issue flow", "throughput"], "jql": 'created >= -30d ORDER BY created DESC', "description": "Issues created in the last 30 days for flow analysis", }, "my_reported_issues": { "phrases": ["my reported", "reported by me", "i created", "i reported"], "jql": 'reporter = currentUser() ORDER BY created DESC', "description": "Issues reported by current user", }, "stale_issues": { "phrases": ["stale", "stale issues", "not updated", "abandoned"], "jql": 'updated <= -30d AND status != Done ORDER BY updated ASC', "description": "Issues not updated in 30+ days", }, "subtasks_without_parent": { "phrases": ["orphan subtasks", "subtasks no parent", "loose subtasks"], "jql": 'type = Sub-task AND parent is EMPTY', "description": "Subtasks missing parent issues", }, "high_priority_unassigned": { "phrases": ["high priority unassigned", "urgent unassigned", "critical no owner"], "jql": 'priority in (Highest, High) AND assignee is EMPTY AND status != Done', "description": "High priority issues with no assignee", }, "bugs_by_component": { "phrases": ["bugs by component", "component bugs"], "jql": 'type = Bug AND status != Done ORDER BY component ASC', "description": "Open bugs organized by component", }, "resolved_recently": { "phrases": ["resolved recently", "recently resolved", "fixed this month"], "jql": 'resolved >= -30d ORDER BY resolved DESC', "description": "Issues resolved in the last 30 days", }, } # Keyword-to-JQL fragment mapping for dynamic query building KEYWORD_FRAGMENTS = { # Issue types "bug": ("type", "= Bug"), "bugs": ("type", "= Bug"), "story": ("type", "= Story"), "stories": ("type", "= Story"), "task": ("type", "= Task"), "tasks": ("type", "= Task"), "epic": ("type", "= Epic"), "epics": ("type", "= Epic"), "subtask": ("type", "= Sub-task"), "sub-task": ("type", "= Sub-task"), # Statuses "open": ("status", "!= Done"), "closed": ("status", "= Done"), "done": ("status", "= Done"), "resolved": ("status", "= Done"), "todo": ("status", '= "To Do"'), # Priorities "critical": ("priority", "= Highest"), "highest": ("priority", "= Highest"), "high": ("priority", "in (Highest, High)"), "medium": ("priority", "= Medium"), "low": ("priority", "in (Low, Lowest)"), "lowest": ("priority", "= Lowest"), # Assignee "me": ("assignee", "= currentUser()"), "mine": ("assignee", "= currentUser()"), "unassigned": ("assignee", "is EMPTY"), # Time "overdue": ("duedate", "< now()"), "today": ("duedate", "= now()"), } PROJECT_PATTERN = re.compile(r'\b([A-Z]{2,10})\b') ASSIGNEE_PATTERN = re.compile(r'assigned\s+to\s+(\w+)', re.IGNORECASE) LABEL_PATTERN = re.compile(r'label[s]?\s*[=:]\s*["\']?(\w+)["\']?', re.IGNORECASE) COMPONENT_PATTERN = re.compile(r'component[s]?\s*[=:]\s*["\']?(\w+)["\']?', re.IGNORECASE) DATE_RANGE_PATTERN = re.compile(r'last\s+(\d+)\s+(day|week|month)s?', re.IGNORECASE) SPRINT_NAME_PATTERN = re.compile(r'sprint\s+["\']?(\w[\w\s]*\w)["\']?', re.IGNORECASE) # Words to exclude from project matching EXCLUDED_WORDS = { "AND", "OR", "NOT", "IN", "IS", "TO", "BY", "ON", "DO", "BE", "THE", "ALL", "MY", "NO", "OF", "AT", "AS", "IF", "IT", "BUG", "BUGS", "TASK", "TASKS", "STORY", "EPIC", "DONE", "HIGH", "LOW", "MEDIUM", "JQL", } # --------------------------------------------------------------------------- # Query Builder # --------------------------------------------------------------------------- def find_matching_pattern(description: str) -> Optional[Dict[str, Any]]: """Check if description matches a known pattern exactly.""" desc_lower = description.lower().strip() for pattern_name, pattern_data in PATTERN_LIBRARY.items(): for phrase in pattern_data["phrases"]: if phrase in desc_lower or desc_lower in phrase: return { "pattern_name": pattern_name, "jql": pattern_data["jql"], "description": pattern_data["description"], "match_type": "exact_pattern", } return None def build_jql_from_description(description: str) -> Dict[str, Any]: """Build JQL query from natural language description.""" # First try exact pattern match pattern_match = find_matching_pattern(description) if pattern_match: # Augment with project if mentioned project = _extract_project(description) if project: pattern_match["jql"] = f'project = {project} AND {pattern_match["jql"]}' return pattern_match # Dynamic query building clauses = [] used_fields = set() desc_lower = description.lower() # Extract project project = _extract_project(description) if project: clauses.append(f"project = {project}") used_fields.add("project") # Extract keyword-based fragments for keyword, (field, fragment) in KEYWORD_FRAGMENTS.items(): if keyword in desc_lower.split() and field not in used_fields: clauses.append(f"{field} {fragment}") used_fields.add(field) # Extract explicit assignee assignee_match = ASSIGNEE_PATTERN.search(description) if assignee_match and "assignee" not in used_fields: assignee = assignee_match.group(1) if assignee.lower() in ("me", "myself"): clauses.append("assignee = currentUser()") else: clauses.append(f'assignee = "{assignee}"') used_fields.add("assignee") # Extract labels label_match = LABEL_PATTERN.search(description) if label_match: clauses.append(f'labels = "{label_match.group(1)}"') # Extract component component_match = COMPONENT_PATTERN.search(description) if component_match: clauses.append(f'component = "{component_match.group(1)}"') # Extract date ranges date_match = DATE_RANGE_PATTERN.search(description) if date_match: amount = date_match.group(1) unit = date_match.group(2).lower() unit_char = {"day": "d", "week": "w", "month": "m"}.get(unit, "d") clauses.append(f"created >= -{amount}{unit_char}") # Extract sprint reference sprint_match = SPRINT_NAME_PATTERN.search(description) if sprint_match: sprint_name = sprint_match.group(1).strip() if sprint_name.lower() in ("current", "active", "open"): clauses.append("sprint in openSprints()") else: clauses.append(f'sprint = "{sprint_name}"') # Default: if no status clause and not looking for done items if "status" not in used_fields and "done" not in desc_lower and "closed" not in desc_lower: clauses.append("status != Done") if not clauses: return { "jql": "", "description": "Could not build query from description", "match_type": "no_match", "error": "No recognizable patterns found in description", } jql = " AND ".join(clauses) # Add ORDER BY for common scenarios if "recent" in desc_lower or "latest" in desc_lower: jql += " ORDER BY created DESC" elif "priority" in desc_lower or "urgent" in desc_lower: jql += " ORDER BY priority DESC" return { "jql": jql, "description": f"Dynamic query from: {description}", "match_type": "dynamic", "clauses_used": len(clauses), } def _extract_project(description: str) -> Optional[str]: """Extract project key from description.""" # Look for IN/in PROJECT pattern in_project = re.search(r'\bin\s+([A-Z]{2,10})\b', description) if in_project and in_project.group(1) not in EXCLUDED_WORDS: return in_project.group(1) # Look for standalone project keys for match in PROJECT_PATTERN.finditer(description): word = match.group(1) if word not in EXCLUDED_WORDS: return word return None def validate_jql_syntax(jql: str) -> Dict[str, Any]: """Basic JQL syntax validation.""" issues = [] if not jql.strip(): return {"valid": False, "issues": ["Empty query"]} # Check balanced quotes single_quotes = jql.count("'") double_quotes = jql.count('"') if single_quotes % 2 != 0: issues.append("Unbalanced single quotes") if double_quotes % 2 != 0: issues.append("Unbalanced double quotes") # Check balanced parentheses open_parens = jql.count("(") close_parens = jql.count(")") if open_parens != close_parens: issues.append(f"Unbalanced parentheses: {open_parens} open, {close_parens} close") # Check for known JQL operators valid_operators = {"=", "!=", ">", "<", ">=", "<=", "~", "!~", "in", "not in", "is", "is not", "was", "was not", "changed"} jql_upper = jql.upper() # Check AND/OR placement if jql_upper.strip().startswith("AND") or jql_upper.strip().startswith("OR"): issues.append("Query cannot start with AND/OR") if jql_upper.strip().endswith("AND") or jql_upper.strip().endswith("OR"): issues.append("Query cannot end with AND/OR") # Check ORDER BY syntax order_match = re.search(r'ORDER\s+BY\s+(\w+)(?:\s+(ASC|DESC))?', jql, re.IGNORECASE) if "ORDER" in jql_upper and not order_match: issues.append("Invalid ORDER BY syntax") return { "valid": len(issues) == 0, "issues": issues, "query_length": len(jql), } # --------------------------------------------------------------------------- # Output Formatting # --------------------------------------------------------------------------- def format_text_output(result: Dict[str, Any]) -> str: """Format results as readable text report.""" lines = [] lines.append("=" * 60) lines.append("JQL QUERY BUILDER RESULTS") lines.append("=" * 60) lines.append("") if "error" in result: lines.append(f"ERROR: {result['error']}") return "\n".join(lines) lines.append(f"Match Type: {result.get('match_type', 'unknown')}") lines.append(f"Description: {result.get('description', '')}") lines.append("") lines.append("GENERATED JQL") lines.append("-" * 30) lines.append(result.get("jql", "")) lines.append("") validation = result.get("validation", {}) if validation: lines.append("VALIDATION") lines.append("-" * 30) lines.append(f"Valid: {'Yes' if validation.get('valid') else 'No'}") if validation.get("issues"): for issue in validation["issues"]: lines.append(f" - {issue}") if result.get("pattern_name"): lines.append("") lines.append(f"Matched Pattern: {result['pattern_name']}") return "\n".join(lines) def format_patterns_output(output_format: str) -> str: """Format available patterns list.""" if output_format == "json": patterns = {} for name, data in PATTERN_LIBRARY.items(): patterns[name] = { "description": data["description"], "phrases": data["phrases"], "jql": data["jql"], } return json.dumps(patterns, indent=2) lines = [] lines.append("=" * 60) lines.append("AVAILABLE JQL PATTERNS") lines.append("=" * 60) lines.append("") for name, data in PATTERN_LIBRARY.items(): lines.append(f" {name}") lines.append(f" Description: {data['description']}") lines.append(f" Phrases: {', '.join(data['phrases'])}") lines.append(f" JQL: {data['jql']}") lines.append("") lines.append(f"Total patterns: {len(PATTERN_LIBRARY)}") return "\n".join(lines) def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]: """Format results as JSON.""" return result # --------------------------------------------------------------------------- # CLI Interface # --------------------------------------------------------------------------- def main() -> int: """Main CLI entry point.""" parser = argparse.ArgumentParser( description="Build JQL queries from natural language descriptions" ) parser.add_argument( "description", nargs="?", help="Natural language description of the query", ) parser.add_argument( "--format", choices=["text", "json"], default="text", help="Output format (default: text)", ) parser.add_argument( "--patterns", action="store_true", help="List all available query patterns", ) args = parser.parse_args() try: if args.patterns: print(format_patterns_output(args.format)) return 0 if not args.description: parser.error("description is required unless --patterns is used") # Build query result = build_jql_from_description(args.description) # Validate if result.get("jql"): result["validation"] = validate_jql_syntax(result["jql"]) # Output results if args.format == "json": output = format_json_output(result) print(json.dumps(output, indent=2)) else: output = format_text_output(result) print(output) return 0 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) FILE:scripts/workflow_validator.py #!/usr/bin/env python3 """ Workflow Validator Validates Jira workflow definitions (JSON input) for anti-patterns and common issues. Checks for dead-end states, orphan states, missing transitions, circular paths, and produces a health score with severity-rated findings. Usage: python workflow_validator.py workflow.json python workflow_validator.py workflow.json --format json """ import argparse import json import sys from typing import Any, Dict, List, Optional, Set, Tuple # --------------------------------------------------------------------------- # Validation Configuration # --------------------------------------------------------------------------- MAX_RECOMMENDED_STATES = 10 REQUIRED_TERMINAL_STATES = {"done", "closed", "resolved", "completed"} SEVERITY_WEIGHTS = { "error": 20, "warning": 10, "info": 3, } # --------------------------------------------------------------------------- # Validation Rules # --------------------------------------------------------------------------- def check_state_count(states: List[str]) -> List[Dict[str, str]]: """Check if the workflow has too many states.""" findings = [] count = len(states) if count > MAX_RECOMMENDED_STATES: findings.append({ "rule": "state_count", "severity": "warning", "message": f"Workflow has {count} states (recommended max: {MAX_RECOMMENDED_STATES}). " f"Complex workflows slow teams down and increase error rates.", }) elif count < 2: findings.append({ "rule": "state_count", "severity": "error", "message": f"Workflow has only {count} state(s). A minimum of 2 states is required.", }) if count > 15: findings[-1]["severity"] = "error" return findings def check_dead_end_states( states: List[str], transitions: List[Dict[str, str]], terminal_states: Set[str], ) -> List[Dict[str, str]]: """Find states with no outgoing transitions that are not terminal.""" findings = [] outgoing = set() for t in transitions: outgoing.add(t.get("from", "").lower()) for state in states: state_lower = state.lower() if state_lower not in outgoing and state_lower not in terminal_states: findings.append({ "rule": "dead_end_state", "severity": "error", "message": f"State '{state}' has no outgoing transitions and is not a terminal state. " f"Issues will get stuck here.", }) return findings def check_orphan_states( states: List[str], transitions: List[Dict[str, str]], initial_state: Optional[str], ) -> List[Dict[str, str]]: """Find states with no incoming transitions (except the initial state).""" findings = [] incoming = set() for t in transitions: incoming.add(t.get("to", "").lower()) initial_lower = (initial_state or "").lower() for state in states: state_lower = state.lower() if state_lower not in incoming and state_lower != initial_lower: findings.append({ "rule": "orphan_state", "severity": "warning", "message": f"State '{state}' has no incoming transitions and is not the initial state. " f"This state may be unreachable.", }) return findings def check_missing_terminal_state(states: List[str]) -> List[Dict[str, str]]: """Check that at least one terminal/done state exists.""" findings = [] states_lower = {s.lower() for s in states} has_terminal = bool(states_lower & REQUIRED_TERMINAL_STATES) if not has_terminal: findings.append({ "rule": "missing_terminal_state", "severity": "error", "message": f"No terminal state found. Expected one of: {', '.join(sorted(REQUIRED_TERMINAL_STATES))}. " f"Issues cannot be marked as complete.", }) return findings def check_duplicate_transition_names( transitions: List[Dict[str, str]], ) -> List[Dict[str, str]]: """Check for duplicate transition names from the same state.""" findings = [] seen = {} for t in transitions: name = t.get("name", "").lower() from_state = t.get("from", "").lower() key = (from_state, name) if key in seen: findings.append({ "rule": "duplicate_transition", "severity": "warning", "message": f"Duplicate transition name '{t.get('name', '')}' from state '{t.get('from', '')}'. " f"This can confuse users selecting transitions.", }) else: seen[key] = True return findings def check_missing_transitions( states: List[str], transitions: List[Dict[str, str]], ) -> List[Dict[str, str]]: """Check for states referenced in transitions but not defined.""" findings = [] defined_states = {s.lower() for s in states} for t in transitions: from_state = t.get("from", "").lower() to_state = t.get("to", "").lower() if from_state and from_state not in defined_states: findings.append({ "rule": "undefined_state_reference", "severity": "error", "message": f"Transition references undefined source state '{t.get('from', '')}'.", }) if to_state and to_state not in defined_states: findings.append({ "rule": "undefined_state_reference", "severity": "error", "message": f"Transition references undefined target state '{t.get('to', '')}'.", }) return findings def check_circular_paths( states: List[str], transitions: List[Dict[str, str]], terminal_states: Set[str], ) -> List[Dict[str, str]]: """Detect circular paths that have no exit to a terminal state.""" findings = [] # Build adjacency list adjacency = {} for state in states: adjacency[state.lower()] = set() for t in transitions: from_state = t.get("from", "").lower() to_state = t.get("to", "").lower() if from_state in adjacency: adjacency[from_state].add(to_state) # Find strongly connected components using iterative DFS def can_reach_terminal(start: str) -> bool: visited = set() stack = [start] while stack: node = stack.pop() if node in terminal_states: return True if node in visited: continue visited.add(node) for neighbor in adjacency.get(node, set()): stack.append(neighbor) return False # Check each non-terminal state for state in states: state_lower = state.lower() if state_lower not in terminal_states: if not can_reach_terminal(state_lower): findings.append({ "rule": "circular_no_exit", "severity": "error", "message": f"State '{state}' cannot reach any terminal state. " f"Issues entering this state will never be resolved.", }) return findings def check_self_transitions(transitions: List[Dict[str, str]]) -> List[Dict[str, str]]: """Check for transitions that go from a state to itself.""" findings = [] for t in transitions: if t.get("from", "").lower() == t.get("to", "").lower(): findings.append({ "rule": "self_transition", "severity": "info", "message": f"State '{t.get('from', '')}' has a self-transition '{t.get('name', '')}'. " f"Ensure this is intentional (e.g., for triggering automation).", }) return findings # --------------------------------------------------------------------------- # Main Validation # --------------------------------------------------------------------------- def validate_workflow(data: Dict[str, Any]) -> Dict[str, Any]: """Run all validations on a workflow definition.""" states = data.get("states", []) transitions = data.get("transitions", []) initial_state = data.get("initial_state", states[0] if states else None) if not states: return { "health_score": 0, "grade": "invalid", "findings": [{"rule": "no_states", "severity": "error", "message": "No states defined in workflow"}], "summary": {"errors": 1, "warnings": 0, "info": 0}, } # Determine terminal states states_lower = {s.lower() for s in states} terminal_states = states_lower & REQUIRED_TERMINAL_STATES # Custom terminal states from input custom_terminals = data.get("terminal_states", []) for ct in custom_terminals: terminal_states.add(ct.lower()) # Run all checks all_findings = [] all_findings.extend(check_state_count(states)) all_findings.extend(check_dead_end_states(states, transitions, terminal_states)) all_findings.extend(check_orphan_states(states, transitions, initial_state)) all_findings.extend(check_missing_terminal_state(states)) all_findings.extend(check_duplicate_transition_names(transitions)) all_findings.extend(check_missing_transitions(states, transitions)) all_findings.extend(check_circular_paths(states, transitions, terminal_states)) all_findings.extend(check_self_transitions(transitions)) # Calculate health score summary = {"errors": 0, "warnings": 0, "info": 0} penalty = 0 for finding in all_findings: severity = finding["severity"] summary[severity] = summary.get(severity, 0) + 1 penalty += SEVERITY_WEIGHTS.get(severity, 0) health_score = max(0, 100 - penalty) if health_score >= 90: grade = "excellent" elif health_score >= 75: grade = "good" elif health_score >= 55: grade = "fair" else: grade = "poor" return { "health_score": health_score, "grade": grade, "findings": all_findings, "summary": summary, "workflow_info": { "state_count": len(states), "transition_count": len(transitions), "initial_state": initial_state, "terminal_states": sorted(terminal_states), }, } # --------------------------------------------------------------------------- # Output Formatting # --------------------------------------------------------------------------- def format_text_output(result: Dict[str, Any]) -> str: """Format results as readable text report.""" lines = [] lines.append("=" * 60) lines.append("WORKFLOW VALIDATION REPORT") lines.append("=" * 60) lines.append("") # Health summary lines.append("HEALTH SUMMARY") lines.append("-" * 30) lines.append(f"Health Score: {result['health_score']}/100") lines.append(f"Grade: {result['grade'].title()}") lines.append("") # Workflow info info = result.get("workflow_info", {}) if info: lines.append("WORKFLOW INFO") lines.append("-" * 30) lines.append(f"States: {info.get('state_count', 0)}") lines.append(f"Transitions: {info.get('transition_count', 0)}") lines.append(f"Initial State: {info.get('initial_state', 'N/A')}") lines.append(f"Terminal States: {', '.join(info.get('terminal_states', []))}") lines.append("") # Summary summary = result.get("summary", {}) lines.append("FINDINGS SUMMARY") lines.append("-" * 30) lines.append(f"Errors: {summary.get('errors', 0)}") lines.append(f"Warnings: {summary.get('warnings', 0)}") lines.append(f"Info: {summary.get('info', 0)}") lines.append("") # Detailed findings findings = result.get("findings", []) if findings: lines.append("DETAILED FINDINGS") lines.append("-" * 30) for i, finding in enumerate(findings, 1): severity = finding["severity"].upper() lines.append(f"{i}. [{severity}] {finding['message']}") lines.append(f" Rule: {finding['rule']}") lines.append("") else: lines.append("No issues found. Workflow looks healthy!") return "\n".join(lines) def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]: """Format results as JSON.""" return result # --------------------------------------------------------------------------- # CLI Interface # --------------------------------------------------------------------------- def main() -> int: """Main CLI entry point.""" parser = argparse.ArgumentParser( description="Validate Jira workflow definitions for anti-patterns" ) parser.add_argument( "workflow_file", help="JSON file containing workflow definition (states, transitions)", ) parser.add_argument( "--format", choices=["text", "json"], default="text", help="Output format (default: text)", ) args = parser.parse_args() try: with open(args.workflow_file, "r") as f: data = json.load(f) result = validate_workflow(data) if args.format == "json": print(json.dumps(format_json_output(result), indent=2)) else: print(format_text_output(result)) return 0 except FileNotFoundError: print(f"Error: File '{args.workflow_file}' not found", file=sys.stderr) return 1 except json.JSONDecodeError as e: print(f"Error: Invalid JSON in '{args.workflow_file}': {e}", file=sys.stderr) return 1 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) FILE:_meta.json { "ownerId": "kn7f2gr00xy51fj1nx2y64ckjs800mhn", "slug": "jira-expert-brajesh", "version": "1.0.1", "publishedAt": 1773243802844 }
Atlassian Jira expert for creating and managing projects, planning, product discovery, JQL queries, workflows, custom fields, automation, reporting, and all...
--- name: "jira-expert" description: Atlassian Jira expert for creating and managing projects, planning, product discovery, JQL queries, workflows, custom fields, automation, reporting, and all Jira features. Use for Jira project setup, configuration, advanced search, dashboard creation, workflow design, and technical Jira operations. --- # Atlassian Jira Expert Master-level expertise in Jira configuration, project management, JQL, workflows, automation, and reporting. Handles all technical and operational aspects of Jira. ## Quick Start — Most Common Operations **Create a project**: ``` mcp jira create_project --name "My Project" --key "MYPROJ" --type scrum --lead "[email protected]" ``` **Run a JQL query**: ``` mcp jira search_issues --jql "project = MYPROJ AND status != Done AND dueDate < now()" --maxResults 50 ``` For full command reference, see [Atlassian MCP Integration](#atlassian-mcp-integration). For JQL functions, see [JQL Functions Reference](#jql-functions-reference). For report templates, see [Reporting Templates](#reporting-templates). --- ## Workflows ### Project Creation 1. Determine project type (Scrum, Kanban, Bug Tracking, etc.) 2. Create project with appropriate template 3. Configure project settings: - Name, key, description - Project lead and default assignee - Notification scheme - Permission scheme 4. Set up issue types and workflows 5. Configure custom fields if needed 6. Create initial board/backlog view 7. **HANDOFF TO**: Scrum Master for team onboarding ### Workflow Design 1. Map out process states (To Do → In Progress → Done) 2. Define transitions and conditions 3. Add validators, post-functions, and conditions 4. Configure workflow scheme 5. **Validate**: Deploy to a test project first; verify all transitions, conditions, and post-functions behave as expected before associating with production projects 6. Associate workflow with project 7. Test workflow with sample issues ### JQL Query Building **Basic Structure**: `field operator value` **Common Operators**: - `=, !=` : equals, not equals - `~, !~` : contains, not contains - `>, <, >=, <=` : comparison - `in, not in` : list membership - `is empty, is not empty` - `was, was in, was not` - `changed` **Powerful JQL Examples**: Find overdue issues: ```jql dueDate < now() AND status != Done ``` Sprint burndown issues: ```jql sprint = 23 AND status changed TO "Done" DURING (startOfSprint(), endOfSprint()) ``` Find stale issues: ```jql updated < -30d AND status != Done ``` Cross-project epic tracking: ```jql "Epic Link" = PROJ-123 ORDER BY rank ``` Velocity calculation: ```jql sprint in closedSprints() AND resolution = Done ``` Team capacity: ```jql assignee in (user1, user2) AND sprint in openSprints() ``` ### Dashboard Creation 1. Create new dashboard (personal or shared) 2. Add relevant gadgets: - Filter Results (JQL-based) - Sprint Burndown - Velocity Chart - Created vs Resolved - Pie Chart (status distribution) 3. Arrange layout for readability 4. Configure automatic refresh 5. Share with appropriate teams 6. **HANDOFF TO**: Senior PM or Scrum Master for use ### Automation Rules 1. Define trigger (issue created, field changed, scheduled) 2. Add conditions (if applicable) 3. Define actions: - Update field - Send notification - Create subtask - Transition issue - Post comment 4. Test automation with sample data 5. Enable and monitor ## Advanced Features ### Custom Fields **When to Create**: - Track data not in standard fields - Capture process-specific information - Enable advanced reporting **Field Types**: Text, Numeric, Date, Select (single/multi/cascading), User picker **Configuration**: 1. Create custom field 2. Configure field context (which projects/issue types) 3. Add to appropriate screens 4. Update search templates if needed ### Issue Linking **Link Types**: - Blocks / Is blocked by - Relates to - Duplicates / Is duplicated by - Clones / Is cloned by - Epic-Story relationship **Best Practices**: - Use Epic linking for feature grouping - Use blocking links to show dependencies - Document link reasons in comments ### Permissions & Security **Permission Schemes**: - Browse Projects - Create/Edit/Delete Issues - Administer Projects - Manage Sprints **Security Levels**: - Define confidential issue visibility - Control access to sensitive data - Audit security changes ### Bulk Operations **Bulk Change**: 1. Use JQL to find target issues 2. Select bulk change operation 3. Choose fields to update 4. **Validate**: Preview all changes before executing; confirm the JQL filter matches only intended issues — bulk edits are difficult to reverse 5. Execute and confirm 6. Monitor background task **Bulk Transitions**: - Move multiple issues through workflow - Useful for sprint cleanup - Requires appropriate permissions - **Validate**: Run the JQL filter and review results in small batches before applying at scale ## JQL Functions Reference > **Tip**: Save frequently used queries as named filters instead of re-running complex JQL ad hoc. See [Best Practices](#best-practices) for performance guidance. **Date**: `startOfDay()`, `endOfDay()`, `startOfWeek()`, `endOfWeek()`, `startOfMonth()`, `endOfMonth()`, `startOfYear()`, `endOfYear()` **Sprint**: `openSprints()`, `closedSprints()`, `futureSprints()` **User**: `currentUser()`, `membersOf("group")` **Advanced**: `issueHistory()`, `linkedIssues()`, `issuesWithFixVersions()` ## Reporting Templates > **Tip**: These JQL snippets can be saved as shared filters or wired directly into Dashboard gadgets (see [Dashboard Creation](#dashboard-creation)). | Report | JQL | |---|---| | Sprint Report | `project = PROJ AND sprint = 23` | | Team Velocity | `assignee in (team) AND sprint in closedSprints() AND resolution = Done` | | Bug Trend | `type = Bug AND created >= -30d` | | Blocker Analysis | `priority = Blocker AND status != Done` | ## Decision Framework **When to Escalate to Atlassian Admin**: - Need new project permission scheme - Require custom workflow scheme across org - User provisioning or deprovisioning - License or billing questions - System-wide configuration changes **When to Collaborate with Scrum Master**: - Sprint board configuration - Backlog prioritization views - Team-specific filters - Sprint reporting needs **When to Collaborate with Senior PM**: - Portfolio-level reporting - Cross-project dashboards - Executive visibility needs - Multi-project dependencies ## Handoff Protocols **FROM Senior PM**: - Project structure requirements - Workflow and field needs - Reporting requirements - Integration needs **TO Senior PM**: - Cross-project metrics - Issue trends and patterns - Workflow bottlenecks - Data quality insights **FROM Scrum Master**: - Sprint board configuration requests - Workflow optimization needs - Backlog filtering requirements - Velocity tracking setup **TO Scrum Master**: - Configured sprint boards - Velocity reports - Burndown charts - Team capacity views ## Best Practices **Data Quality**: - Enforce required fields with field validation rules - Use consistent issue key naming conventions per project type - Schedule regular cleanup of stale/orphaned issues **Performance**: - Avoid leading wildcards in JQL (`~` on large text fields is expensive) - Use saved filters instead of re-running complex JQL ad hoc - Limit dashboard gadgets to reduce page load time - Archive completed projects rather than deleting to preserve history **Governance**: - Document rationale for custom workflow states and transitions - Version-control permission/workflow schemes before making changes - Require change management review for org-wide scheme updates - Run permission audits after user role changes ## Atlassian MCP Integration **Primary Tool**: Jira MCP Server **Key Operations with Example Commands**: Create a project: ``` mcp jira create_project --name "My Project" --key "MYPROJ" --type scrum --lead "[email protected]" ``` Execute a JQL query: ``` mcp jira search_issues --jql "project = MYPROJ AND status != Done AND dueDate < now()" --maxResults 50 ``` Update an issue field: ``` mcp jira update_issue --issue "MYPROJ-42" --field "status" --value "In Progress" ``` Create a sprint: ``` mcp jira create_sprint --board 10 --name "Sprint 5" --startDate "2024-06-01" --endDate "2024-06-14" ``` Create a board filter: ``` mcp jira create_filter --name "Open Blockers" --jql "priority = Blocker AND status != Done" --shareWith "project-team" ``` **Integration Points**: - Pull metrics for Senior PM reporting - Configure sprint boards for Scrum Master - Create documentation pages for Confluence Expert - Support template creation for Template Creator ## Related Skills - **Confluence Expert** (`project-management/confluence-expert/`) — Documentation complements Jira workflows - **Atlassian Admin** (`project-management/atlassian-admin/`) — Permission and user management for Jira projects FILE:references/automation-examples.md # Jira Automation Examples ## Auto-Assignment Rules ### Auto-assign by component **Trigger:** Issue created **Conditions:** - Component is not EMPTY **Actions:** - Assign issue to component lead ### Auto-assign to reporter for feedback **Trigger:** Issue transitioned to "Waiting for Feedback" **Actions:** - Assign issue to reporter - Add comment: "Please provide additional information" ### Round-robin assignment **Trigger:** Issue created **Conditions:** - Project = ABC - Assignee is EMPTY **Actions:** - Assign to next team member in rotation (use smart value) --- ## Status Sync Rules ### Sync subtask status to parent **Trigger:** Issue transitioned **Conditions:** - Issue type = Sub-task - Transition is to "Done" - Parent issue exists - All subtasks are Done **Actions:** - Transition parent issue to "Done" ### Sync parent to subtasks **Trigger:** Issue transitioned **Conditions:** - Issue type has subtasks - Transition is to "Cancelled" **Actions:** - For each: Sub-tasks - Transition issue to "Cancelled" ### Epic progress tracking **Trigger:** Issue transitioned **Conditions:** - Epic link is not EMPTY - Transition is to "Done" **Actions:** - Add comment to epic: "{{issue.key}} completed" - Update epic custom field "Progress" --- ## Notification Rules ### Slack notification for high-priority bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority IN (Highest, High) **Actions:** - Send Slack message to #engineering: ``` 🚨 High Priority Bug Created {{issue.key}}: {{issue.summary}} Reporter: {{issue.reporter.displayName}} Priority: {{issue.priority.name}} {{issue.url}} ``` ### Email assignee when mentioned **Trigger:** Issue commented **Conditions:** - Comment contains @mention of assignee **Actions:** - Send email to {{issue.assignee.emailAddress}}: ``` Subject: You were mentioned in {{issue.key}} Body: {{comment.author.displayName}} mentioned you: {{comment.body}} ``` ### SLA breach warning **Trigger:** Scheduled - Every hour **Conditions:** - Status != Done - SLA time remaining < 2 hours **Actions:** - Send email to {{issue.assignee}} - Add comment: "⚠️ SLA expires in <2 hours" - Set priority to Highest --- ## Field Automation Rules ### Auto-set due date **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority = Highest **Actions:** - Set due date to {{now.plusDays(1)}} ### Clear assignee when in backlog **Trigger:** Issue transitioned **Conditions:** - Transition is to "Backlog" - Assignee is not EMPTY **Actions:** - Assign issue to Unassigned - Add comment: "Returned to backlog, assignee cleared" ### Auto-populate sprint field **Trigger:** Issue transitioned **Conditions:** - Transition is to "In Progress" - Sprint is EMPTY **Actions:** - Add issue to current sprint ### Set fix version based on component **Trigger:** Issue created **Conditions:** - Component = "Mobile App" **Actions:** - Set fix version to "Mobile v2.0" --- ## Escalation Rules ### Auto-escalate stale issues **Trigger:** Scheduled - Daily at 9:00 AM **Conditions:** - Status = "Waiting for Response" - Updated < -7 days **Actions:** - Add comment: "@{{issue.reporter}} This issue needs attention" - Send email to project lead - Add label: "needs-attention" ### Escalate overdue critical issues **Trigger:** Scheduled - Every hour **Conditions:** - Priority IN (Highest, High) - Due date < now() - Status != Done **Actions:** - Transition to "Escalated" - Assign to project manager - Send Slack notification ### Auto-close inactive issues **Trigger:** Scheduled - Daily at 10:00 AM **Conditions:** - Status = "Waiting for Customer" - Updated < -30 days **Actions:** - Transition to "Closed" - Add comment: "Auto-closed due to inactivity" - Send email to reporter --- ## Sprint Automation Rules ### Move incomplete work to next sprint **Trigger:** Sprint closed **Conditions:** - Issue status != Done **Actions:** - Add issue to next sprint - Add comment: "Moved from {{sprint.name}}" ### Auto-remove completed items from active sprint **Trigger:** Issue transitioned **Conditions:** - Transition is to "Done" - Sprint IN openSprints() **Actions:** - Remove issue from sprint - Add comment: "Removed from active sprint (completed)" ### Sprint start notification **Trigger:** Sprint started **Actions:** - Send Slack message to #team: ``` 🚀 Sprint {{sprint.name}} Started! Goal: {{sprint.goal}} Committed: {{sprint.issuesCount}} issues ``` --- ## Approval Workflow Rules ### Request approval for large stories **Trigger:** Issue created **Conditions:** - Issue type = Story - Story points >= 13 **Actions:** - Transition to "Pending Approval" - Assign to product owner - Send email notification ### Auto-approve small bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Priority IN (Low, Lowest) **Actions:** - Transition to "Approved" - Add comment: "Auto-approved (low-priority bug)" ### Require security review **Trigger:** Issue transitioned **Conditions:** - Transition is to "Ready for Release" - Labels contains "security" **Actions:** - Transition to "Security Review" - Assign to security-team - Send email to [email protected] --- ## Integration Rules ### Create GitHub issue **Trigger:** Issue transitioned **Conditions:** - Transition is to "In Progress" - Labels contains "needs-tracking" **Actions:** - Send webhook to GitHub API: ```json { "title": "{{issue.key}}: {{issue.summary}}", "body": "{{issue.description}}", "assignee": "{{issue.assignee.name}}" } ``` ### Update Confluence page **Trigger:** Issue transitioned **Conditions:** - Issue type = Epic - Transition is to "Done" **Actions:** - Send webhook to Confluence: - Update epic status page - Add completion date --- ## Quality & Testing Rules ### Require test cases for features **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Transition is to "Ready for QA" - Custom field "Test Cases" is EMPTY **Actions:** - Transition back to "In Progress" - Add comment: "❌ Test cases required before QA" ### Auto-create test issue **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Transition is to "Ready for QA" **Actions:** - Create linked issue: - Type: Test - Summary: "Test: {{issue.summary}}" - Link type: "tested by" - Assignee: QA team ### Flag regression bugs **Trigger:** Issue created **Conditions:** - Issue type = Bug - Affects version is in released versions **Actions:** - Add label: "regression" - Set priority to High - Add comment: "🚨 Regression in released version" --- ## Documentation Rules ### Require documentation for features **Trigger:** Issue transitioned **Conditions:** - Issue type = Story - Labels contains "customer-facing" - Transition is to "Done" - Custom field "Documentation Link" is EMPTY **Actions:** - Reopen issue - Add comment: "📝 Documentation required for customer-facing feature" ### Auto-create doc task **Trigger:** Issue transitioned **Conditions:** - Issue type = Epic - Transition is to "In Progress" **Actions:** - Create subtask: - Type: Task - Summary: "Documentation for {{issue.summary}}" - Assignee: {{issue.assignee}} --- ## Time Tracking Rules ### Log work reminder **Trigger:** Issue transitioned **Conditions:** - Transition is to "Done" - Time spent is EMPTY **Actions:** - Add comment: "⏱️ Reminder: Please log your time" ### Warn on high time spent **Trigger:** Work logged **Conditions:** - Time spent > original estimate * 1.5 **Actions:** - Add comment: "⚠️ Time spent exceeds estimate by 50%" - Send notification to assignee and project manager --- ## Advanced Conditional Rules ### Conditional assignee based on priority **Trigger:** Issue created **Conditions:** - Issue type = Bug **Actions:** - If: Priority = Highest - Assign to on-call engineer - Else if: Priority = High - Assign to team lead - Else: - Assign to next available team member ### Multi-step approval flow **Trigger:** Issue transitioned **Conditions:** - Transition is to "Request Approval" - Budget estimate > $10,000 **Actions:** - If: Budget > $50,000 - Assign to CFO - Send email to executive team - Else if: Budget > $10,000 - Assign to Director - Add comment: "Director approval required" - Add label: "pending-approval" --- ## Smart Value Examples ### Dynamic assignee based on component ``` {{issue.components.first.lead.accountId}} ``` ### Days since created ``` {{issue.created.diff(now).days}} ``` ### Conditional message ``` {{#if(issue.priority.name == "Highest")}} 🚨 CRITICAL {{else}} ℹ️ Normal priority {{/}} ``` ### List all subtasks ``` {{#issue.subtasks}} - {{key}}: {{summary}} ({{status.name}}) {{/}} ``` ### Calculate completion percentage ``` {{issue.subtasks.filter(item => item.status.statusCategory.key == "done").size.divide(issue.subtasks.size).multiply(100).round()}}% ``` --- ## Best Practices 1. **Test in sandbox** - Always test rules on test project first 2. **Start simple** - Begin with basic rules, add complexity incrementally 3. **Use conditions wisely** - Narrow scope to reduce unintended triggers 4. **Monitor audit log** - Check automation execution history regularly 5. **Limit actions** - Keep rules focused, don't chain too many actions 6. **Name clearly** - Use descriptive names: "Auto-assign bugs to component lead" 7. **Document rules** - Add description explaining purpose and owner 8. **Review regularly** - Audit rules quarterly, disable unused ones 9. **Handle errors** - Add error handling for webhooks and integrations 10. **Performance** - Avoid scheduled rules that query large datasets hourly FILE:references/AUTOMATION.md # Jira Automation Reference Comprehensive guide to Jira automation rules: triggers, conditions, actions, smart values, and production-ready recipes. ## Rule Structure Every automation rule follows this pattern: ``` TRIGGER → [CONDITION(s)] → ACTION(s) ``` - **Trigger**: The event that starts the rule (required, exactly one) - **Condition**: Filters to narrow when the rule fires (optional, multiple allowed) - **Action**: What the rule does (required, one or more) ## Triggers ### Issue Triggers | Trigger | Fires When | Use For | |---------|------------|---------| | **Issue created** | New issue is created | Auto-assignment, notifications, SLA start | | **Issue transitioned** | Status changes | Workflow automation, notifications | | **Issue updated** | Any field changes | Field sync, cascading updates | | **Issue commented** | Comment is added | Auto-responses, SLA tracking | | **Issue assigned** | Assignee changes | Workload notifications | | **Issue linked** | Link is added/removed | Dependency tracking | | **Issue deleted** | Issue is deleted | Cleanup, audit logging | ### Sprint & Board Triggers | Trigger | Fires When | |---------|------------| | **Sprint started** | Sprint is activated | | **Sprint completed** | Sprint is closed | | **Issue moved between sprints** | Issue is moved | | **Backlog item moved to sprint** | Item is pulled into sprint | ### Scheduled Triggers | Trigger | Fires When | |---------|------------| | **Scheduled** | Cron-based (daily, weekly, custom) | | **Issue stale** | No updates for X days | ### Version Triggers | Trigger | Fires When | |---------|------------| | **Version created** | New version added | | **Version released** | Version is released | ## Conditions ### Issue Conditions | Condition | Matches When | |-----------|-------------| | **Issue fields condition** | Field matches value (e.g., priority = High) | | **JQL condition** | Issue matches JQL query | | **Related issues condition** | Linked/sub-task issues match criteria | | **User condition** | Actor matches (reporter, assignee, group) | | **Advanced compare** | Complex field comparisons | ### Condition Operators ``` Field = value # Exact match Field != value # Not equal Field > value # Greater than (numeric/date) Field is empty # Field has no value Field is not empty # Field has a value Field changed # Field was modified in this event Field changed to # Field changed to specific value Field changed from # Field changed from specific value ``` ## Actions ### Issue Actions | Action | Does | |--------|------| | **Edit issue** | Update any field on the current issue | | **Transition issue** | Move to a new status | | **Assign issue** | Change assignee | | **Comment on issue** | Add a comment | | **Create issue** | Create a new linked issue | | **Create sub-tasks** | Create child issues | | **Clone issue** | Duplicate the issue | | **Delete issue** | Remove the issue | | **Link issues** | Add issue links | | **Log work** | Add time tracking entry | ### Notification Actions | Action | Does | |--------|------| | **Send email** | Send custom email to users/groups | | **Send Slack message** | Post to Slack channel (requires integration) | | **Send Microsoft Teams message** | Post to Teams (requires integration) | | **Send web request** | HTTP call to external service | ### Lookup & Branch Actions | Action | Does | |--------|------| | **Lookup issues (JQL)** | Find issues matching JQL, iterate over them | | **Create branch** | Branch logic (if/then/else) | | **For each** | Loop over found issues | ## Smart Values Smart values are dynamic placeholders that resolve at runtime. ### Issue Smart Values ``` {{issue.key}} # PROJ-123 {{issue.summary}} # Issue title {{issue.description}} # Full description {{issue.status.name}} # Current status {{issue.priority.name}} # Priority level {{issue.assignee.displayName}} # Assignee name {{issue.reporter.displayName}} # Reporter name {{issue.issuetype.name}} # Issue type {{issue.project.key}} # Project key {{issue.created}} # Creation date {{issue.updated}} # Last update date {{issue.fixVersions}} # Fix versions {{issue.labels}} # Labels array {{issue.components}} # Components array ``` ### Transition Smart Values ``` {{transition.from_status}} # Previous status {{transition.to_status}} # New status {{transition.transitionName}} # Transition name ``` ### User Smart Values ``` {{initiator.displayName}} # Who triggered the rule {{initiator.emailAddress}} # Their email {{initiator.accountId}} # Their account ID ``` ### Date Smart Values ``` {{now}} # Current timestamp {{now.plusDays(7)}} # 7 days from now {{now.minusHours(24)}} # 24 hours ago {{issue.created.plusBusinessDays(3)}} # 3 business days after creation ``` ### Conditional Smart Values ``` {{#if issue.priority.name == "High"}} This is high priority {{/if}} {{#if issue.assignee}} Assigned to {{issue.assignee.displayName}} {{else}} Unassigned {{/if}} ``` ## Production-Ready Recipes ### 1. Auto-Assign by Component ```yaml Trigger: Issue created Condition: Issue has component Action: Edit issue - Assignee = Component lead Rule Logic: IF component = "Backend" → assign to @backend-lead IF component = "Frontend" → assign to @frontend-lead IF component = "DevOps" → assign to @devops-lead ``` ### 2. SLA Warning — Stale Issues ```yaml Trigger: Scheduled (daily at 9am) Condition: JQL = "status != Done AND updated <= -5d AND priority in (High, Highest)" Action: - Add comment: "⚠️ This {{issue.priority.name}} issue hasn't been updated in 5+ days." - Send Slack: "#engineering-alerts: {{issue.key}} is stale ({{issue.assignee.displayName}})" ``` ### 3. Auto-Close Resolved Issues After 7 Days ```yaml Trigger: Scheduled (daily) Condition: JQL = "status = Resolved AND updated <= -7d" Action: - Transition: Resolved → Closed - Comment: "Auto-closed after 7 days in Resolved status." ``` ### 4. Sprint Spillover Notification ```yaml Trigger: Sprint completed Condition: Issue status != Done Action: - Comment: "Spilled over from Sprint {{sprint.name}}. Reason needs review." - Add label: "spillover" - Send email to: {{issue.assignee.emailAddress}} ``` ### 5. Sub-Task Completion → Parent Transition ```yaml Trigger: Issue transitioned (to Done) Condition: Issue is sub-task AND all sibling sub-tasks are Done Action (on parent): - Transition: In Progress → In Review - Comment: "All sub-tasks completed. Ready for review." ``` ### 6. Bug Priority Escalation ```yaml Trigger: Scheduled (every 4 hours) Condition: JQL = "type = Bug AND priority = High AND status = Open AND created <= -24h" Action: - Edit: priority = Highest - Comment: "⚡ Auto-escalated: High-priority bug open for 24+ hours." - Send email to: project lead ``` ### 7. Auto-Link Duplicate Detection ```yaml Trigger: Issue created Condition: JQL finds issues with similar summary (fuzzy) Action: - Comment: "Possible duplicate of {{lookupIssues.first.key}}: {{lookupIssues.first.summary}}" - Add label: "possible-duplicate" ``` ### 8. Release Notes Generator ```yaml Trigger: Version released Condition: None Action: - Lookup: JQL = "fixVersion = {{version.name}} AND status = Done" - Create Confluence page: Title: "Release Notes — {{version.name}}" Content: List of resolved issues with types and summaries ``` ### 9. Workload Balancer — Round-Robin Assignment ```yaml Trigger: Issue created Condition: Issue type = Story AND assignee is empty Action: - Lookup: JQL = "assignee in (dev1, dev2, dev3) AND sprint in openSprints() AND status != Done" - Assign to team member with fewest open issues ``` ### 10. Blocker Notification Chain ```yaml Trigger: Issue updated (priority changed to Blocker) Action: - Send email to: project lead, scrum master - Send Slack: "#blockers: 🚨 {{issue.key}} marked as Blocker by {{initiator.displayName}}" - Comment: "Blocker escalated. Notified: PM + SM." - Edit: Add label "blocker-active" ``` ## Best Practices 1. **Name rules descriptively** — "Auto-assign Backend bugs to @dev-lead" not "Rule 1" 2. **Add conditions before actions** — prevent unintended execution 3. **Use JQL conditions** for precision — field conditions can miss edge cases 4. **Test in a sandbox project first** — automation mistakes can be destructive 5. **Set rate limits** — avoid infinite loops (Rule A triggers Rule B triggers Rule A) 6. **Monitor rule execution** — check Automation audit log weekly 7. **Document business rules** — explain WHY the rule exists, not just WHAT it does 8. **Use branches (if/else)** over separate rules — reduces rule count, easier to maintain 9. **Disable before deleting** — observe for a week to ensure no side effects 10. **Version your automation** — export rules as JSON backup before major changes FILE:references/jql-examples.md # JQL Query Examples ## Sprint Queries **Current sprint issues:** ```jql sprint IN openSprints() ORDER BY rank ``` **Issues in specific sprint:** ```jql sprint = "Sprint 23" ORDER BY priority DESC ``` **All sprint work (current and backlog):** ```jql project = ABC AND issuetype IN (Story, Bug, Task) ORDER BY sprint DESC, rank ``` **Unscheduled stories:** ```jql project = ABC AND issuetype = Story AND sprint IS EMPTY AND status != Done ORDER BY priority DESC ``` **Spillover from last sprint:** ```jql sprint IN closedSprints() AND sprint NOT IN (latestReleasedVersion()) AND status != Done ORDER BY created DESC ``` **Sprint completion rate:** ```jql sprint = "Sprint 23" AND status = Done ``` ## User & Team Queries **My open issues:** ```jql assignee = currentUser() AND status != Done ORDER BY priority DESC, created ASC ``` **Unassigned in my project:** ```jql project = ABC AND assignee IS EMPTY AND status != Done ORDER BY priority DESC ``` **Issues I'm watching:** ```jql watcher = currentUser() AND status != Done ``` **Team workload:** ```jql assignee IN membersOf("engineering-team") AND status IN ("In Progress", "In Review") ORDER BY assignee, priority DESC ``` **Issues I reported that are still open:** ```jql reporter = currentUser() AND status != Done ORDER BY created DESC ``` **Issues commented on by me:** ```jql comment ~ currentUser() AND status != Done ``` ## Date Range Queries **Created today:** ```jql created >= startOfDay() ORDER BY created DESC ``` **Updated in last 7 days:** ```jql updated >= -7d ORDER BY updated DESC ``` **Created this week:** ```jql created >= startOfWeek() AND created <= endOfWeek() ``` **Created this month:** ```jql created >= startOfMonth() AND created <= endOfMonth() ``` **Not updated in 30 days:** ```jql status != Done AND updated <= -30d ORDER BY updated ASC ``` **Resolved yesterday:** ```jql resolved >= startOfDay(-1d) AND resolved < startOfDay() ``` **Due this week:** ```jql duedate >= startOfWeek() AND duedate <= endOfWeek() AND status != Done ``` **Overdue:** ```jql duedate < now() AND status != Done ORDER BY duedate ASC ``` ## Status & Workflow Queries **In Progress issues:** ```jql project = ABC AND status = "In Progress" ORDER BY assignee ``` **Blocked issues:** ```jql project = ABC AND labels = blocked AND status != Done ``` **Issues in review:** ```jql project = ABC AND status IN ("Code Review", "QA Review", "Pending Approval") ORDER BY updated ASC ``` **Ready for development:** ```jql project = ABC AND status = "Ready" AND sprint IS EMPTY ORDER BY priority DESC ``` **Recently done:** ```jql project = ABC AND status = Done AND resolved >= -7d ORDER BY resolved DESC ``` **Status changed today:** ```jql status CHANGED AFTER startOfDay() ORDER BY updated DESC ``` **Long-running in progress:** ```jql status = "In Progress" AND status CHANGED BEFORE -14d ORDER BY status CHANGED ASC ``` ## Priority & Type Queries **High priority bugs:** ```jql issuetype = Bug AND priority IN (Highest, High) AND status != Done ORDER BY priority DESC, created ASC ``` **Critical blockers:** ```jql priority = Highest AND status != Done ORDER BY created ASC ``` **All epics:** ```jql issuetype = Epic ORDER BY status, priority DESC ``` **Stories without acceptance criteria:** ```jql issuetype = Story AND "Acceptance Criteria" IS EMPTY AND status = Backlog ``` **Technical debt:** ```jql labels = tech-debt AND status != Done ORDER BY priority DESC ``` ## Complex Multi-Condition Queries **My team's sprint work:** ```jql sprint IN openSprints() AND assignee IN membersOf("engineering-team") AND status != Done ORDER BY assignee, priority DESC ``` **Bugs created this month, not in sprint:** ```jql issuetype = Bug AND created >= startOfMonth() AND sprint IS EMPTY AND status != Done ORDER BY priority DESC, created DESC ``` **High-priority work needing attention:** ```jql project = ABC AND priority IN (Highest, High) AND status IN ("In Progress", "In Review") AND updated <= -3d ORDER BY priority DESC, updated ASC ``` **Stale issues:** ```jql project = ABC AND status NOT IN (Done, Cancelled) AND (assignee IS EMPTY OR updated <= -30d) ORDER BY created ASC ``` **Epic progress:** ```jql "Epic Link" = ABC-123 ORDER BY status, rank ``` ## Component & Version Queries **Issues in component:** ```jql project = ABC AND component = "Frontend" AND status != Done ``` **Issues without component:** ```jql project = ABC AND component IS EMPTY AND status != Done ``` **Target version:** ```jql fixVersion = "v2.0" ORDER BY status, priority DESC ``` **Released versions:** ```jql fixVersion IN releasedVersions() ORDER BY fixVersion DESC ``` ## Label & Text Search Queries **Issues with label:** ```jql labels = urgent AND status != Done ``` **Multiple labels (AND):** ```jql labels IN (frontend, bug) AND status != Done ``` **Search in summary:** ```jql summary ~ "authentication" ORDER BY created DESC ``` **Search in summary and description:** ```jql text ~ "API integration" ORDER BY created DESC ``` **Issues with empty description:** ```jql description IS EMPTY AND issuetype = Story ``` ## Performance-Optimized Queries **Good - Specific project first:** ```jql project = ABC AND status = "In Progress" AND assignee = currentUser() ``` **Bad - User filter first:** ```jql assignee = currentUser() AND status = "In Progress" AND project = ABC ``` **Good - Use functions:** ```jql sprint IN openSprints() AND status != Done ``` **Bad - Hardcoded sprint:** ```jql sprint = "Sprint 23" AND status != Done ``` **Good - Specific date:** ```jql created >= 2024-01-01 AND created <= 2024-01-31 ``` **Bad - Relative with high cost:** ```jql created >= -365d AND created <= -335d ``` ## Reporting Queries **Velocity calculation:** ```jql sprint = "Sprint 23" AND status = Done ``` *Then sum story points* **Bug rate:** ```jql project = ABC AND issuetype = Bug AND created >= startOfMonth() ``` **Average cycle time:** ```jql project = ABC AND resolved >= startOfMonth() AND resolved <= endOfMonth() ``` *Calculate time from In Progress to Done* **Stories delivered this quarter:** ```jql project = ABC AND issuetype = Story AND resolved >= startOfYear() AND resolved <= endOfQuarter() ``` **Team capacity:** ```jql assignee IN membersOf("engineering-team") AND sprint IN openSprints() ``` *Sum original estimates* ## Notification & Watching Queries **Issues I need to review:** ```jql status = "Pending Review" AND assignee = currentUser() ``` **Issues assigned to me, high priority:** ```jql assignee = currentUser() AND priority IN (Highest, High) AND status != Done ``` **Issues created by me, not resolved:** ```jql reporter = currentUser() AND status != Done ORDER BY created DESC ``` ## Advanced Functions **Issues changed from status:** ```jql status WAS "In Progress" AND status = "Done" AND status CHANGED AFTER startOfWeek() ``` **Assignee changed:** ```jql assignee CHANGED BY currentUser() AFTER -7d ``` **Issues re-opened:** ```jql status WAS Done AND status != Done ORDER BY updated DESC ``` **Linked issues:** ```jql issue IN linkedIssues("ABC-123") ORDER BY issuetype ``` **Parent epic:** ```jql parent = ABC-123 ORDER BY rank ``` ## Saved Filter Examples **Daily Standup Filter:** ```jql assignee = currentUser() AND sprint IN openSprints() AND status != Done ORDER BY priority DESC ``` **Team Sprint Board Filter:** ```jql project = ABC AND sprint IN openSprints() ORDER BY rank ``` **Bugs Dashboard Filter:** ```jql project = ABC AND issuetype = Bug AND status != Done ORDER BY priority DESC, created ASC ``` **Tech Debt Backlog:** ```jql project = ABC AND labels = tech-debt AND status = Backlog ORDER BY priority DESC ``` **Needs Triage:** ```jql project = ABC AND status = "To Triage" AND created >= -7d ORDER BY created ASC ``` FILE:references/WORKFLOWS.md # Jira Workflows Reference Comprehensive guide to Jira workflow design, transitions, conditions, validators, and post-functions. ## Default Workflows ### Simplified Workflow ``` Open → In Progress → Done ``` ### Software Development Workflow ``` Backlog → Selected for Development → In Progress → In Review → Done ↑___________________________| (reopen) ``` ### Bug Tracking Workflow ``` Open → In Progress → Fixed → Verified → Closed ↑ | | |____Reopened________|________| ``` ## Custom Workflow Design ### Design Principles 1. **Mirror your actual process** — don't force teams into artificial states 2. **Minimize statuses** — each status must represent a distinct work state where the item waits for a different action 3. **Clear ownership** — every status should have an obvious responsible party 4. **Allow rework** — always provide paths back for rejected/reopened items 5. **Separate "waiting" from "working"** — distinguish "In Review" (waiting) from "Reviewing" (actively working) ### Status Categories Jira maps every status to one of four categories that drive board columns and JQL: | Category | Meaning | JQL | Examples | |----------|---------|-----|----------| | `To Do` | Not started | `statusCategory = "To Do"` | Backlog, Open, New | | `In Progress` | Active work | `statusCategory = "In Progress"` | In Progress, In Review, Testing | | `Done` | Completed | `statusCategory = Done` | Done, Closed, Released | | `Undefined` | Legacy/unused | — | Avoid using | ### Recommended Statuses by Team Type **Engineering Team:** ``` Backlog → Ready → In Progress → Code Review → QA → Done ``` **Support Team:** ``` New → Triaged → In Progress → Waiting on Customer → Resolved → Closed ``` **Design Team:** ``` Backlog → Research → Design → Review → Approved → Handoff ``` ## Transitions ### Transition Properties | Property | Description | |----------|-------------| | **Name** | Display name on the button (e.g., "Start Work") | | **Screen** | Form shown during transition (optional) | | **Conditions** | Who can trigger this transition | | **Validators** | Rules that must pass before transition executes | | **Post-functions** | Actions executed after transition completes | ### Common Transition Patterns **Start Work:** ``` Trigger: "Start Work" button Condition: Assignee only Validator: Issue must have assignee Post-function: Set "In Progress" resolution to None ``` **Submit for Review:** ``` Trigger: "Submit for Review" button Condition: Assignee or project admin Validator: All sub-tasks must be Done Post-function: Add comment "Submitted for review by {user}" ``` **Approve:** ``` Trigger: "Approve" button Condition: Must be in "Reviewers" group Validator: Must add comment Post-function: Set resolution to "Done", fire event ``` ## Conditions ### Built-in Conditions | Condition | Use When | |-----------|----------| | **Only Assignee** | Only assigned user can transition | | **Only Reporter** | Only creator can transition | | **Permission Condition** | User must have specific permission | | **Group Condition** | User must be in specified group | | **Sub-Task Blocking** | All sub-tasks must be resolved | | **Previous Status** | Issue must have been in a specific status | | **User Is In Role** | User must have project role (Developer, Admin) | ### Combining Conditions - **AND logic**: Add multiple conditions to one transition — ALL must pass - **OR logic**: Create parallel transitions with different conditions ## Validators ### Built-in Validators | Validator | Checks | |-----------|--------| | **Required Field** | Specific field must be populated | | **Field Has Been Modified** | Field must change during transition | | **Regular Expression** | Field must match regex pattern | | **Permission Validator** | User must have permission | | **Previous Status Validator** | Issue was in a required status | ### Common Validator Patterns ``` # Require comment on rejection Validator: Comment Required When: Transition = "Reject" # Require fix version before release Validator: Required Field = "Fix Version/s" When: Transition = "Release" # Require time logged before closing Validator: Field Required = "Time Spent" (must be > 0) When: Transition = "Close" ``` ## Post-Functions ### Built-in Post-Functions | Post-Function | Action | |---------------|--------| | **Set Field Value** | Assign a value to any field | | **Update Issue Field** | Change assignee, priority, etc. | | **Create Comment** | Add automated comment | | **Fire Event** | Trigger notification event | | **Assign to Lead** | Assign to project lead | | **Assign to Reporter** | Assign back to creator | | **Clear Field** | Remove field value | | **Copy Value** | Copy field from parent/linked issue | ### Post-Function Execution Order Post-functions execute in defined order. Standard sequence: 1. Set issue status (automatic, always first) 2. Add comment (if configured) 3. Update fields 4. Generate change history (automatic, always last) 5. Fire event (triggers notifications) **Important:** "Generate change history" and "Fire event" must always be last — reorder if you add custom post-functions. ## Workflow Schemes ### What They Do - Map issue types to workflows within a project - One workflow scheme per project - Different issue types can use different workflows ### Configuration Pattern ``` Project: MYPROJ Workflow Scheme: "Engineering Workflow Scheme" Bug → Bug Tracking Workflow Story → Development Workflow Task → Simple Workflow Epic → Epic Workflow Sub-task → Sub-task Workflow (inherits parent transitions) ``` ## Best Practices 1. **Start simple, add complexity only when needed** — a 5-status workflow beats a 15-status one 2. **Name transitions as actions** — "Start Work" not "In Progress" (the status is "In Progress", the action is "Start Work") 3. **Use screens sparingly** — only show a screen when you need data from the user during transition 4. **Test with real users** — workflows that look good on paper may confuse the team 5. **Document your workflow** — add descriptions to statuses and transitions 6. **Use global transitions carefully** — a "Cancel" transition from any status is convenient but can bypass important gates 7. **Audit quarterly** — remove statuses with <5% usage FILE:scripts/jql_query_builder.py #!/usr/bin/env python3 """ JQL Query Builder Pattern-matching JQL builder from natural language descriptions. Maps common phrases to JQL operators and constructs valid queries with syntax validation. Usage: python jql_query_builder.py "high priority bugs in PROJECT assigned to me" python jql_query_builder.py "overdue tasks in PROJ" --format json python jql_query_builder.py --patterns """ import argparse import json import re import sys from datetime import datetime from typing import Any, Dict, List, Optional, Tuple # --------------------------------------------------------------------------- # Pattern Library # --------------------------------------------------------------------------- PATTERN_LIBRARY = { "my_open_bugs": { "phrases": ["my open bugs", "my bugs", "bugs assigned to me"], "jql": 'assignee = currentUser() AND type = Bug AND status != Done', "description": "All open bugs assigned to current user", }, "high_priority_bugs": { "phrases": ["high priority bugs", "critical bugs", "urgent bugs", "p1 bugs"], "jql": 'type = Bug AND priority in (Highest, High) AND status != Done', "description": "High and highest priority open bugs", }, "my_open_tasks": { "phrases": ["my open tasks", "my tasks", "tasks assigned to me", "my work"], "jql": 'assignee = currentUser() AND status != Done', "description": "All open issues assigned to current user", }, "unassigned_issues": { "phrases": ["unassigned", "unassigned issues", "no assignee"], "jql": 'assignee is EMPTY AND status != Done', "description": "Issues with no assignee", }, "recently_created": { "phrases": ["recently created", "new issues", "created this week", "recent"], "jql": 'created >= -7d ORDER BY created DESC', "description": "Issues created in the last 7 days", }, "recently_updated": { "phrases": ["recently updated", "updated this week", "recent changes"], "jql": 'updated >= -7d ORDER BY updated DESC', "description": "Issues updated in the last 7 days", }, "overdue": { "phrases": ["overdue", "past due", "missed deadline", "overdue tasks"], "jql": 'duedate < now() AND status != Done', "description": "Issues past their due date", }, "due_this_week": { "phrases": ["due this week", "due soon", "upcoming deadlines"], "jql": 'duedate >= startOfWeek() AND duedate <= endOfWeek() AND status != Done', "description": "Issues due this week", }, "blocked_issues": { "phrases": ["blocked", "blocked issues", "impediments"], "jql": 'status = Blocked OR status = Impediment', "description": "Issues in blocked or impediment status", }, "in_progress": { "phrases": ["in progress", "being worked on", "active work"], "jql": 'status = "In Progress"', "description": "Issues currently in progress", }, "sprint_issues": { "phrases": ["current sprint", "this sprint", "active sprint"], "jql": 'sprint in openSprints()', "description": "Issues in the current active sprint", }, "backlog": { "phrases": ["backlog", "backlog items", "not started"], "jql": 'sprint is EMPTY AND status = "To Do" ORDER BY priority DESC', "description": "Issues in the backlog not assigned to a sprint", }, "stories_without_estimates": { "phrases": ["no estimates", "unestimated", "missing estimates", "no story points"], "jql": 'type = Story AND (storyPoints is EMPTY OR storyPoints = 0) AND status != Done', "description": "Stories missing story point estimates", }, "epics_in_progress": { "phrases": ["active epics", "epics in progress", "open epics"], "jql": 'type = Epic AND status != Done ORDER BY priority DESC', "description": "Epics that are not yet completed", }, "done_this_week": { "phrases": ["done this week", "completed this week", "resolved this week"], "jql": 'status changed to Done DURING (startOfWeek(), now())', "description": "Issues completed during the current week", }, "created_vs_resolved": { "phrases": ["created vs resolved", "issue flow", "throughput"], "jql": 'created >= -30d ORDER BY created DESC', "description": "Issues created in the last 30 days for flow analysis", }, "my_reported_issues": { "phrases": ["my reported", "reported by me", "i created", "i reported"], "jql": 'reporter = currentUser() ORDER BY created DESC', "description": "Issues reported by current user", }, "stale_issues": { "phrases": ["stale", "stale issues", "not updated", "abandoned"], "jql": 'updated <= -30d AND status != Done ORDER BY updated ASC', "description": "Issues not updated in 30+ days", }, "subtasks_without_parent": { "phrases": ["orphan subtasks", "subtasks no parent", "loose subtasks"], "jql": 'type = Sub-task AND parent is EMPTY', "description": "Subtasks missing parent issues", }, "high_priority_unassigned": { "phrases": ["high priority unassigned", "urgent unassigned", "critical no owner"], "jql": 'priority in (Highest, High) AND assignee is EMPTY AND status != Done', "description": "High priority issues with no assignee", }, "bugs_by_component": { "phrases": ["bugs by component", "component bugs"], "jql": 'type = Bug AND status != Done ORDER BY component ASC', "description": "Open bugs organized by component", }, "resolved_recently": { "phrases": ["resolved recently", "recently resolved", "fixed this month"], "jql": 'resolved >= -30d ORDER BY resolved DESC', "description": "Issues resolved in the last 30 days", }, } # Keyword-to-JQL fragment mapping for dynamic query building KEYWORD_FRAGMENTS = { # Issue types "bug": ("type", "= Bug"), "bugs": ("type", "= Bug"), "story": ("type", "= Story"), "stories": ("type", "= Story"), "task": ("type", "= Task"), "tasks": ("type", "= Task"), "epic": ("type", "= Epic"), "epics": ("type", "= Epic"), "subtask": ("type", "= Sub-task"), "sub-task": ("type", "= Sub-task"), # Statuses "open": ("status", "!= Done"), "closed": ("status", "= Done"), "done": ("status", "= Done"), "resolved": ("status", "= Done"), "todo": ("status", '= "To Do"'), # Priorities "critical": ("priority", "= Highest"), "highest": ("priority", "= Highest"), "high": ("priority", "in (Highest, High)"), "medium": ("priority", "= Medium"), "low": ("priority", "in (Low, Lowest)"), "lowest": ("priority", "= Lowest"), # Assignee "me": ("assignee", "= currentUser()"), "mine": ("assignee", "= currentUser()"), "unassigned": ("assignee", "is EMPTY"), # Time "overdue": ("duedate", "< now()"), "today": ("duedate", "= now()"), } PROJECT_PATTERN = re.compile(r'\b([A-Z]{2,10})\b') ASSIGNEE_PATTERN = re.compile(r'assigned\s+to\s+(\w+)', re.IGNORECASE) LABEL_PATTERN = re.compile(r'label[s]?\s*[=:]\s*["\']?(\w+)["\']?', re.IGNORECASE) COMPONENT_PATTERN = re.compile(r'component[s]?\s*[=:]\s*["\']?(\w+)["\']?', re.IGNORECASE) DATE_RANGE_PATTERN = re.compile(r'last\s+(\d+)\s+(day|week|month)s?', re.IGNORECASE) SPRINT_NAME_PATTERN = re.compile(r'sprint\s+["\']?(\w[\w\s]*\w)["\']?', re.IGNORECASE) # Words to exclude from project matching EXCLUDED_WORDS = { "AND", "OR", "NOT", "IN", "IS", "TO", "BY", "ON", "DO", "BE", "THE", "ALL", "MY", "NO", "OF", "AT", "AS", "IF", "IT", "BUG", "BUGS", "TASK", "TASKS", "STORY", "EPIC", "DONE", "HIGH", "LOW", "MEDIUM", "JQL", } # --------------------------------------------------------------------------- # Query Builder # --------------------------------------------------------------------------- def find_matching_pattern(description: str) -> Optional[Dict[str, Any]]: """Check if description matches a known pattern exactly.""" desc_lower = description.lower().strip() for pattern_name, pattern_data in PATTERN_LIBRARY.items(): for phrase in pattern_data["phrases"]: if phrase in desc_lower or desc_lower in phrase: return { "pattern_name": pattern_name, "jql": pattern_data["jql"], "description": pattern_data["description"], "match_type": "exact_pattern", } return None def build_jql_from_description(description: str) -> Dict[str, Any]: """Build JQL query from natural language description.""" # First try exact pattern match pattern_match = find_matching_pattern(description) if pattern_match: # Augment with project if mentioned project = _extract_project(description) if project: pattern_match["jql"] = f'project = {project} AND {pattern_match["jql"]}' return pattern_match # Dynamic query building clauses = [] used_fields = set() desc_lower = description.lower() # Extract project project = _extract_project(description) if project: clauses.append(f"project = {project}") used_fields.add("project") # Extract keyword-based fragments for keyword, (field, fragment) in KEYWORD_FRAGMENTS.items(): if keyword in desc_lower.split() and field not in used_fields: clauses.append(f"{field} {fragment}") used_fields.add(field) # Extract explicit assignee assignee_match = ASSIGNEE_PATTERN.search(description) if assignee_match and "assignee" not in used_fields: assignee = assignee_match.group(1) if assignee.lower() in ("me", "myself"): clauses.append("assignee = currentUser()") else: clauses.append(f'assignee = "{assignee}"') used_fields.add("assignee") # Extract labels label_match = LABEL_PATTERN.search(description) if label_match: clauses.append(f'labels = "{label_match.group(1)}"') # Extract component component_match = COMPONENT_PATTERN.search(description) if component_match: clauses.append(f'component = "{component_match.group(1)}"') # Extract date ranges date_match = DATE_RANGE_PATTERN.search(description) if date_match: amount = date_match.group(1) unit = date_match.group(2).lower() unit_char = {"day": "d", "week": "w", "month": "m"}.get(unit, "d") clauses.append(f"created >= -{amount}{unit_char}") # Extract sprint reference sprint_match = SPRINT_NAME_PATTERN.search(description) if sprint_match: sprint_name = sprint_match.group(1).strip() if sprint_name.lower() in ("current", "active", "open"): clauses.append("sprint in openSprints()") else: clauses.append(f'sprint = "{sprint_name}"') # Default: if no status clause and not looking for done items if "status" not in used_fields and "done" not in desc_lower and "closed" not in desc_lower: clauses.append("status != Done") if not clauses: return { "jql": "", "description": "Could not build query from description", "match_type": "no_match", "error": "No recognizable patterns found in description", } jql = " AND ".join(clauses) # Add ORDER BY for common scenarios if "recent" in desc_lower or "latest" in desc_lower: jql += " ORDER BY created DESC" elif "priority" in desc_lower or "urgent" in desc_lower: jql += " ORDER BY priority DESC" return { "jql": jql, "description": f"Dynamic query from: {description}", "match_type": "dynamic", "clauses_used": len(clauses), } def _extract_project(description: str) -> Optional[str]: """Extract project key from description.""" # Look for IN/in PROJECT pattern in_project = re.search(r'\bin\s+([A-Z]{2,10})\b', description) if in_project and in_project.group(1) not in EXCLUDED_WORDS: return in_project.group(1) # Look for standalone project keys for match in PROJECT_PATTERN.finditer(description): word = match.group(1) if word not in EXCLUDED_WORDS: return word return None def validate_jql_syntax(jql: str) -> Dict[str, Any]: """Basic JQL syntax validation.""" issues = [] if not jql.strip(): return {"valid": False, "issues": ["Empty query"]} # Check balanced quotes single_quotes = jql.count("'") double_quotes = jql.count('"') if single_quotes % 2 != 0: issues.append("Unbalanced single quotes") if double_quotes % 2 != 0: issues.append("Unbalanced double quotes") # Check balanced parentheses open_parens = jql.count("(") close_parens = jql.count(")") if open_parens != close_parens: issues.append(f"Unbalanced parentheses: {open_parens} open, {close_parens} close") # Check for known JQL operators valid_operators = {"=", "!=", ">", "<", ">=", "<=", "~", "!~", "in", "not in", "is", "is not", "was", "was not", "changed"} jql_upper = jql.upper() # Check AND/OR placement if jql_upper.strip().startswith("AND") or jql_upper.strip().startswith("OR"): issues.append("Query cannot start with AND/OR") if jql_upper.strip().endswith("AND") or jql_upper.strip().endswith("OR"): issues.append("Query cannot end with AND/OR") # Check ORDER BY syntax order_match = re.search(r'ORDER\s+BY\s+(\w+)(?:\s+(ASC|DESC))?', jql, re.IGNORECASE) if "ORDER" in jql_upper and not order_match: issues.append("Invalid ORDER BY syntax") return { "valid": len(issues) == 0, "issues": issues, "query_length": len(jql), } # --------------------------------------------------------------------------- # Output Formatting # --------------------------------------------------------------------------- def format_text_output(result: Dict[str, Any]) -> str: """Format results as readable text report.""" lines = [] lines.append("=" * 60) lines.append("JQL QUERY BUILDER RESULTS") lines.append("=" * 60) lines.append("") if "error" in result: lines.append(f"ERROR: {result['error']}") return "\n".join(lines) lines.append(f"Match Type: {result.get('match_type', 'unknown')}") lines.append(f"Description: {result.get('description', '')}") lines.append("") lines.append("GENERATED JQL") lines.append("-" * 30) lines.append(result.get("jql", "")) lines.append("") validation = result.get("validation", {}) if validation: lines.append("VALIDATION") lines.append("-" * 30) lines.append(f"Valid: {'Yes' if validation.get('valid') else 'No'}") if validation.get("issues"): for issue in validation["issues"]: lines.append(f" - {issue}") if result.get("pattern_name"): lines.append("") lines.append(f"Matched Pattern: {result['pattern_name']}") return "\n".join(lines) def format_patterns_output(output_format: str) -> str: """Format available patterns list.""" if output_format == "json": patterns = {} for name, data in PATTERN_LIBRARY.items(): patterns[name] = { "description": data["description"], "phrases": data["phrases"], "jql": data["jql"], } return json.dumps(patterns, indent=2) lines = [] lines.append("=" * 60) lines.append("AVAILABLE JQL PATTERNS") lines.append("=" * 60) lines.append("") for name, data in PATTERN_LIBRARY.items(): lines.append(f" {name}") lines.append(f" Description: {data['description']}") lines.append(f" Phrases: {', '.join(data['phrases'])}") lines.append(f" JQL: {data['jql']}") lines.append("") lines.append(f"Total patterns: {len(PATTERN_LIBRARY)}") return "\n".join(lines) def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]: """Format results as JSON.""" return result # --------------------------------------------------------------------------- # CLI Interface # --------------------------------------------------------------------------- def main() -> int: """Main CLI entry point.""" parser = argparse.ArgumentParser( description="Build JQL queries from natural language descriptions" ) parser.add_argument( "description", nargs="?", help="Natural language description of the query", ) parser.add_argument( "--format", choices=["text", "json"], default="text", help="Output format (default: text)", ) parser.add_argument( "--patterns", action="store_true", help="List all available query patterns", ) args = parser.parse_args() try: if args.patterns: print(format_patterns_output(args.format)) return 0 if not args.description: parser.error("description is required unless --patterns is used") # Build query result = build_jql_from_description(args.description) # Validate if result.get("jql"): result["validation"] = validate_jql_syntax(result["jql"]) # Output results if args.format == "json": output = format_json_output(result) print(json.dumps(output, indent=2)) else: output = format_text_output(result) print(output) return 0 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) FILE:scripts/workflow_validator.py #!/usr/bin/env python3 """ Workflow Validator Validates Jira workflow definitions (JSON input) for anti-patterns and common issues. Checks for dead-end states, orphan states, missing transitions, circular paths, and produces a health score with severity-rated findings. Usage: python workflow_validator.py workflow.json python workflow_validator.py workflow.json --format json """ import argparse import json import sys from typing import Any, Dict, List, Optional, Set, Tuple # --------------------------------------------------------------------------- # Validation Configuration # --------------------------------------------------------------------------- MAX_RECOMMENDED_STATES = 10 REQUIRED_TERMINAL_STATES = {"done", "closed", "resolved", "completed"} SEVERITY_WEIGHTS = { "error": 20, "warning": 10, "info": 3, } # --------------------------------------------------------------------------- # Validation Rules # --------------------------------------------------------------------------- def check_state_count(states: List[str]) -> List[Dict[str, str]]: """Check if the workflow has too many states.""" findings = [] count = len(states) if count > MAX_RECOMMENDED_STATES: findings.append({ "rule": "state_count", "severity": "warning", "message": f"Workflow has {count} states (recommended max: {MAX_RECOMMENDED_STATES}). " f"Complex workflows slow teams down and increase error rates.", }) elif count < 2: findings.append({ "rule": "state_count", "severity": "error", "message": f"Workflow has only {count} state(s). A minimum of 2 states is required.", }) if count > 15: findings[-1]["severity"] = "error" return findings def check_dead_end_states( states: List[str], transitions: List[Dict[str, str]], terminal_states: Set[str], ) -> List[Dict[str, str]]: """Find states with no outgoing transitions that are not terminal.""" findings = [] outgoing = set() for t in transitions: outgoing.add(t.get("from", "").lower()) for state in states: state_lower = state.lower() if state_lower not in outgoing and state_lower not in terminal_states: findings.append({ "rule": "dead_end_state", "severity": "error", "message": f"State '{state}' has no outgoing transitions and is not a terminal state. " f"Issues will get stuck here.", }) return findings def check_orphan_states( states: List[str], transitions: List[Dict[str, str]], initial_state: Optional[str], ) -> List[Dict[str, str]]: """Find states with no incoming transitions (except the initial state).""" findings = [] incoming = set() for t in transitions: incoming.add(t.get("to", "").lower()) initial_lower = (initial_state or "").lower() for state in states: state_lower = state.lower() if state_lower not in incoming and state_lower != initial_lower: findings.append({ "rule": "orphan_state", "severity": "warning", "message": f"State '{state}' has no incoming transitions and is not the initial state. " f"This state may be unreachable.", }) return findings def check_missing_terminal_state(states: List[str]) -> List[Dict[str, str]]: """Check that at least one terminal/done state exists.""" findings = [] states_lower = {s.lower() for s in states} has_terminal = bool(states_lower & REQUIRED_TERMINAL_STATES) if not has_terminal: findings.append({ "rule": "missing_terminal_state", "severity": "error", "message": f"No terminal state found. Expected one of: {', '.join(sorted(REQUIRED_TERMINAL_STATES))}. " f"Issues cannot be marked as complete.", }) return findings def check_duplicate_transition_names( transitions: List[Dict[str, str]], ) -> List[Dict[str, str]]: """Check for duplicate transition names from the same state.""" findings = [] seen = {} for t in transitions: name = t.get("name", "").lower() from_state = t.get("from", "").lower() key = (from_state, name) if key in seen: findings.append({ "rule": "duplicate_transition", "severity": "warning", "message": f"Duplicate transition name '{t.get('name', '')}' from state '{t.get('from', '')}'. " f"This can confuse users selecting transitions.", }) else: seen[key] = True return findings def check_missing_transitions( states: List[str], transitions: List[Dict[str, str]], ) -> List[Dict[str, str]]: """Check for states referenced in transitions but not defined.""" findings = [] defined_states = {s.lower() for s in states} for t in transitions: from_state = t.get("from", "").lower() to_state = t.get("to", "").lower() if from_state and from_state not in defined_states: findings.append({ "rule": "undefined_state_reference", "severity": "error", "message": f"Transition references undefined source state '{t.get('from', '')}'.", }) if to_state and to_state not in defined_states: findings.append({ "rule": "undefined_state_reference", "severity": "error", "message": f"Transition references undefined target state '{t.get('to', '')}'.", }) return findings def check_circular_paths( states: List[str], transitions: List[Dict[str, str]], terminal_states: Set[str], ) -> List[Dict[str, str]]: """Detect circular paths that have no exit to a terminal state.""" findings = [] # Build adjacency list adjacency = {} for state in states: adjacency[state.lower()] = set() for t in transitions: from_state = t.get("from", "").lower() to_state = t.get("to", "").lower() if from_state in adjacency: adjacency[from_state].add(to_state) # Find strongly connected components using iterative DFS def can_reach_terminal(start: str) -> bool: visited = set() stack = [start] while stack: node = stack.pop() if node in terminal_states: return True if node in visited: continue visited.add(node) for neighbor in adjacency.get(node, set()): stack.append(neighbor) return False # Check each non-terminal state for state in states: state_lower = state.lower() if state_lower not in terminal_states: if not can_reach_terminal(state_lower): findings.append({ "rule": "circular_no_exit", "severity": "error", "message": f"State '{state}' cannot reach any terminal state. " f"Issues entering this state will never be resolved.", }) return findings def check_self_transitions(transitions: List[Dict[str, str]]) -> List[Dict[str, str]]: """Check for transitions that go from a state to itself.""" findings = [] for t in transitions: if t.get("from", "").lower() == t.get("to", "").lower(): findings.append({ "rule": "self_transition", "severity": "info", "message": f"State '{t.get('from', '')}' has a self-transition '{t.get('name', '')}'. " f"Ensure this is intentional (e.g., for triggering automation).", }) return findings # --------------------------------------------------------------------------- # Main Validation # --------------------------------------------------------------------------- def validate_workflow(data: Dict[str, Any]) -> Dict[str, Any]: """Run all validations on a workflow definition.""" states = data.get("states", []) transitions = data.get("transitions", []) initial_state = data.get("initial_state", states[0] if states else None) if not states: return { "health_score": 0, "grade": "invalid", "findings": [{"rule": "no_states", "severity": "error", "message": "No states defined in workflow"}], "summary": {"errors": 1, "warnings": 0, "info": 0}, } # Determine terminal states states_lower = {s.lower() for s in states} terminal_states = states_lower & REQUIRED_TERMINAL_STATES # Custom terminal states from input custom_terminals = data.get("terminal_states", []) for ct in custom_terminals: terminal_states.add(ct.lower()) # Run all checks all_findings = [] all_findings.extend(check_state_count(states)) all_findings.extend(check_dead_end_states(states, transitions, terminal_states)) all_findings.extend(check_orphan_states(states, transitions, initial_state)) all_findings.extend(check_missing_terminal_state(states)) all_findings.extend(check_duplicate_transition_names(transitions)) all_findings.extend(check_missing_transitions(states, transitions)) all_findings.extend(check_circular_paths(states, transitions, terminal_states)) all_findings.extend(check_self_transitions(transitions)) # Calculate health score summary = {"errors": 0, "warnings": 0, "info": 0} penalty = 0 for finding in all_findings: severity = finding["severity"] summary[severity] = summary.get(severity, 0) + 1 penalty += SEVERITY_WEIGHTS.get(severity, 0) health_score = max(0, 100 - penalty) if health_score >= 90: grade = "excellent" elif health_score >= 75: grade = "good" elif health_score >= 55: grade = "fair" else: grade = "poor" return { "health_score": health_score, "grade": grade, "findings": all_findings, "summary": summary, "workflow_info": { "state_count": len(states), "transition_count": len(transitions), "initial_state": initial_state, "terminal_states": sorted(terminal_states), }, } # --------------------------------------------------------------------------- # Output Formatting # --------------------------------------------------------------------------- def format_text_output(result: Dict[str, Any]) -> str: """Format results as readable text report.""" lines = [] lines.append("=" * 60) lines.append("WORKFLOW VALIDATION REPORT") lines.append("=" * 60) lines.append("") # Health summary lines.append("HEALTH SUMMARY") lines.append("-" * 30) lines.append(f"Health Score: {result['health_score']}/100") lines.append(f"Grade: {result['grade'].title()}") lines.append("") # Workflow info info = result.get("workflow_info", {}) if info: lines.append("WORKFLOW INFO") lines.append("-" * 30) lines.append(f"States: {info.get('state_count', 0)}") lines.append(f"Transitions: {info.get('transition_count', 0)}") lines.append(f"Initial State: {info.get('initial_state', 'N/A')}") lines.append(f"Terminal States: {', '.join(info.get('terminal_states', []))}") lines.append("") # Summary summary = result.get("summary", {}) lines.append("FINDINGS SUMMARY") lines.append("-" * 30) lines.append(f"Errors: {summary.get('errors', 0)}") lines.append(f"Warnings: {summary.get('warnings', 0)}") lines.append(f"Info: {summary.get('info', 0)}") lines.append("") # Detailed findings findings = result.get("findings", []) if findings: lines.append("DETAILED FINDINGS") lines.append("-" * 30) for i, finding in enumerate(findings, 1): severity = finding["severity"].upper() lines.append(f"{i}. [{severity}] {finding['message']}") lines.append(f" Rule: {finding['rule']}") lines.append("") else: lines.append("No issues found. Workflow looks healthy!") return "\n".join(lines) def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]: """Format results as JSON.""" return result # --------------------------------------------------------------------------- # CLI Interface # --------------------------------------------------------------------------- def main() -> int: """Main CLI entry point.""" parser = argparse.ArgumentParser( description="Validate Jira workflow definitions for anti-patterns" ) parser.add_argument( "workflow_file", help="JSON file containing workflow definition (states, transitions)", ) parser.add_argument( "--format", choices=["text", "json"], default="text", help="Output format (default: text)", ) args = parser.parse_args() try: with open(args.workflow_file, "r") as f: data = json.load(f) result = validate_workflow(data) if args.format == "json": print(json.dumps(format_json_output(result), indent=2)) else: print(format_text_output(result)) return 0 except FileNotFoundError: print(f"Error: File '{args.workflow_file}' not found", file=sys.stderr) return 1 except json.JSONDecodeError as e: print(f"Error: Invalid JSON in '{args.workflow_file}': {e}", file=sys.stderr) return 1 except Exception as e: print(f"Error: {e}", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) FILE:_meta.json { "ownerId": "kn7f2gr00xy51fj1nx2y64ckjs800mhn", "slug": "jira-expert-brajesh-1", "version": "1.0.0", "publishedAt": 1773243802844 }