
Jira Expert
- 729 installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
Jira-expert is a Claude Code skill that supplies copy-ready Jira automation rule patterns for auto-assignment, parent-subtask status sync, and Slack alerts for developers running tickets alongside coding agents.
About
Jira-expert is a reference skill from alirezarezvani/claude-skills with ready-made Jira Automation rule patterns developers can copy and adapt. It covers auto-assignment by component lead, reporter reassignment on Waiting for Feedback, and round-robin assignment using smart values for empty assignees. Status-sync examples wire subtask Done transitions to parent closure when all children complete, and parent transitions that cascade to subtasks. Additional patterns include Slack notification hooks when issues move across workflows. Developers reach for jira-expert when standing up or refining Jira Automation without memorizing trigger-condition-action syntax. The skill pairs with agent-driven development where tickets track parallel workstreams. It documents concrete rule blocks—triggers, conditions, and actions—rather than abstract agile advice, so operators paste patterns into Jira Cloud automation editors and tune project keys or statuses.
- Auto-assign by component lead, reporter on feedback, and round-robin for unassigned ABC-project issues
- Parent/subtask status sync when all subtasks finish or when a parent is cancelled
- Epic progress comments and custom-field updates when linked issues reach Done
- High-priority bug Slack notifications to #engineering with structured template
Jira Expert by the numbers
- 729 all-time installs (skills.sh)
- Ranked #348 of 2,725 Automation & Workflows skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill jira-expertAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 729 |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you automate Jira assignment and status sync?
Copy and adapt Jira automation rule patterns for auto-assignment, parent–subtask status sync, and Slack alerts when you run tickets alongside your agent.
Who is it for?
Developers operating Jira Cloud boards who need paste-ready automation rules for assignment and status workflows.
Skip if: Teams without Jira Automation access or developers who only need issue creation from git commits.
When should I use this skill?
Jira tickets need auto-assignment, parent-subtask status sync, or Slack alerts configured via automation rules.
What you get
Jira Automation rule definitions for auto-assignment, parent-subtask sync, and Slack notification actions
- Jira Automation rule templates
Files
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
All MCP examples in this skill use the real Atlassian Remote MCP tools (camelCase, surfaced as mcp__atlassian__<toolName>). The canonical tool list is project-management/references/atlassian-mcp-tools.md — never invent tool names; if a capability isn't listed there, it is not available via MCP.
Create an issue (call getAccessibleAtlassianResources once first to obtain cloudId):
mcp__atlassian__createJiraIssue (cloudId, projectKey="MYPROJ", issueTypeName="Story", summary="My new story")Run a JQL query (build the JQL from natural language with the bundled script, then execute):
python3 scripts/jql_query_builder.py "high priority bugs assigned to me"
# → emits validated JQL, e.g.: assignee = currentUser() AND type = Bug AND status != Donemcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = MYPROJ AND status != Done AND dueDate < now()")Create a project: NOT available via MCP. Use the Jira web UI (Projects > Create project) or REST API (POST /rest/api/3/project).
For the full tool reference, see Atlassian MCP Integration. For JQL functions, see JQL Functions Reference. For report templates, see Reporting Templates.
---
Workflows
Project Creation
Project creation is not available via MCP — perform steps 2-6 in the Jira web UI (Projects > Create project) or via REST API (POST /rest/api/3/project). After creation, verify visibility withmcp__atlassian__getVisibleJiraProjectsand inspect issue types withmcp__atlassian__getJiraProjectIssueTypesMetadata.
1. Determine project type (Scrum, Kanban, Bug Tracking, etc.) 2. Create project with appropriate template (web UI / REST) 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
Workflow/scheme editing is not available via MCP — configure in Jira Settings > Issues > Workflows. Use the bundled validator to catch anti-patterns before deploying.1. Map out process states (To Do → In Progress → Done) 2. Define transitions and conditions 3. Lint the design before building it in Jira:
python3 scripts/workflow_validator.py workflow.json --format jsonInput: a JSON file with the workflow's states and transitions. Consume the output: fix every reported anti-pattern (dead-end states, unreachable states, missing transitions) in the design before touching Jira. 4. Add validators, post-functions, and conditions; configure the workflow scheme (web UI) 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 — via MCP: mcp__atlassian__getTransitionsForJiraIssue on a sample issue to confirm expected transitions surface, then mcp__atlassian__transitionJiraIssue to walk it through the flow
JQL Query Building
Start with the bundled builder — it pattern-matches natural language to validated JQL:
python3 scripts/jql_query_builder.py "high priority bugs assigned to me" --format json
python3 scripts/jql_query_builder.py --patterns # list all supported query patternsConsume the output: take the jql field from the JSON result (or the GENERATED JQL block in text mode) and execute it with mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql=<generated>). If the builder reports no pattern match, compose JQL manually using the reference below.
Basic Structure: field operator value
Common Operators:
=, !=: equals, not equals~, !~: contains, not contains>, <, >=, <=: comparisonin, not in: list membershipis empty, is not emptywas, was in, was notchanged
Powerful JQL Examples:
Find overdue issues:
dueDate < now() AND status != DoneSprint burndown issues:
sprint = 23 AND status changed TO "Done" DURING (startOfSprint(), endOfSprint())Find stale issues:
updated < -30d AND status != DoneCross-project epic tracking:
"Epic Link" = PROJ-123 ORDER BY rankVelocity calculation:
sprint in closedSprints() AND resolution = DoneTeam capacity:
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 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).
| 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: Atlassian Remote MCP server (bundled .mcp.json, server key atlassian). Tools surface as mcp__atlassian__<toolName>. Canonical tool list: project-management/references/atlassian-mcp-tools.md. Never invent tool names — if a capability isn't in that list, route to the web UI/REST API.
Key Operations with Example Calls (obtain cloudId once via mcp__atlassian__getAccessibleAtlassianResources):
Create an issue (check required fields first with getJiraIssueTypeMetaWithFields):
mcp__atlassian__createJiraIssue (cloudId, projectKey="MYPROJ", issueTypeName="Story", summary="My new story")Execute a JQL query:
mcp__atlassian__searchJiraIssuesUsingJql (cloudId, jql="project = MYPROJ AND status != Done AND dueDate < now()")Update an issue field:
mcp__atlassian__editJiraIssue (cloudId, issueIdOrKey="MYPROJ-42", fields=<payload — discover via tool schema>)Transition an issue (status changes go through transitions, not field edits):
mcp__atlassian__getTransitionsForJiraIssue (cloudId, issueIdOrKey="MYPROJ-42")
mcp__atlassian__transitionJiraIssue (cloudId, issueIdOrKey="MYPROJ-42", transition=<id from previous call>)Comment / log work / link issues:
mcp__atlassian__addCommentToJiraIssue (cloudId, issueIdOrKey="MYPROJ-42", body="...")
mcp__atlassian__addWorklogToJiraIssue (cloudId, issueIdOrKey="MYPROJ-42", timeSpent=<discover via tool schema>)
mcp__atlassian__createIssueLink (cloudId, link type from mcp__atlassian__getIssueLinkTypes)Not available via MCP — use the web UI or REST API instead:
- Create a project → Jira UI
Projects > Create projectorPOST /rest/api/3/project - Create a sprint or configure boards → Jira Software UI or
POST /rest/agile/1.0/sprint - Create/share a filter → Jira UI
Filters > Save asorPOST /rest/api/3/filter - Custom fields, screens, workflow/permission schemes → Jira admin UI
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
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 security@company.com
---
Integration Rules
Create GitHub issue
Trigger: Issue transitioned Conditions:
- Transition is to "In Progress"
- Labels contains "needs-tracking"
Actions:
- Send webhook to GitHub API:
{
"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
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 valueActions
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 arrayTransition Smart Values
{{transition.from_status}} # Previous status
{{transition.to_status}} # New status
{{transition.transitionName}} # Transition nameUser Smart Values
{{initiator.displayName}} # Who triggered the rule
{{initiator.emailAddress}} # Their email
{{initiator.accountId}} # Their account IDDate 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 creationConditional 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
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-lead2. SLA Warning — Stale Issues
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
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
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
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
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 lead7. Auto-Link Duplicate Detection
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
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 summaries9. Workload Balancer — Round-Robin Assignment
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 issues10. Blocker Notification Chain
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
JQL Query Examples
Sprint Queries
Current sprint issues:
sprint IN openSprints() ORDER BY rankIssues in specific sprint:
sprint = "Sprint 23" ORDER BY priority DESCAll sprint work (current and backlog):
project = ABC AND issuetype IN (Story, Bug, Task)
ORDER BY sprint DESC, rankUnscheduled stories:
project = ABC AND issuetype = Story AND sprint IS EMPTY
AND status != Done ORDER BY priority DESCSpillover from last sprint:
sprint IN closedSprints() AND sprint NOT IN (latestReleasedVersion())
AND status != Done ORDER BY created DESCSprint completion rate:
sprint = "Sprint 23" AND status = DoneUser & Team Queries
My open issues:
assignee = currentUser() AND status != Done
ORDER BY priority DESC, created ASCUnassigned in my project:
project = ABC AND assignee IS EMPTY AND status != Done
ORDER BY priority DESCIssues I'm watching:
watcher = currentUser() AND status != DoneTeam workload:
assignee IN membersOf("engineering-team") AND status IN ("In Progress", "In Review")
ORDER BY assignee, priority DESCIssues I reported that are still open:
reporter = currentUser() AND status != Done ORDER BY created DESCIssues commented on by me:
comment ~ currentUser() AND status != DoneDate Range Queries
Created today:
created >= startOfDay() ORDER BY created DESCUpdated in last 7 days:
updated >= -7d ORDER BY updated DESCCreated this week:
created >= startOfWeek() AND created <= endOfWeek()Created this month:
created >= startOfMonth() AND created <= endOfMonth()Not updated in 30 days:
status != Done AND updated <= -30d ORDER BY updated ASCResolved yesterday:
resolved >= startOfDay(-1d) AND resolved < startOfDay()Due this week:
duedate >= startOfWeek() AND duedate <= endOfWeek() AND status != DoneOverdue:
duedate < now() AND status != Done ORDER BY duedate ASCStatus & Workflow Queries
In Progress issues:
project = ABC AND status = "In Progress" ORDER BY assigneeBlocked issues:
project = ABC AND labels = blocked AND status != DoneIssues in review:
project = ABC AND status IN ("Code Review", "QA Review", "Pending Approval")
ORDER BY updated ASCReady for development:
project = ABC AND status = "Ready" AND sprint IS EMPTY
ORDER BY priority DESCRecently done:
project = ABC AND status = Done AND resolved >= -7d
ORDER BY resolved DESCStatus changed today:
status CHANGED AFTER startOfDay() ORDER BY updated DESCLong-running in progress:
status = "In Progress" AND status CHANGED BEFORE -14d
ORDER BY status CHANGED ASCPriority & Type Queries
High priority bugs:
issuetype = Bug AND priority IN (Highest, High) AND status != Done
ORDER BY priority DESC, created ASCCritical blockers:
priority = Highest AND status != Done ORDER BY created ASCAll epics:
issuetype = Epic ORDER BY status, priority DESCStories without acceptance criteria:
issuetype = Story AND "Acceptance Criteria" IS EMPTY AND status = BacklogTechnical debt:
labels = tech-debt AND status != Done ORDER BY priority DESCComplex Multi-Condition Queries
My team's sprint work:
sprint IN openSprints()
AND assignee IN membersOf("engineering-team")
AND status != Done
ORDER BY assignee, priority DESCBugs created this month, not in sprint:
issuetype = Bug
AND created >= startOfMonth()
AND sprint IS EMPTY
AND status != Done
ORDER BY priority DESC, created DESCHigh-priority work needing attention:
project = ABC
AND priority IN (Highest, High)
AND status IN ("In Progress", "In Review")
AND updated <= -3d
ORDER BY priority DESC, updated ASCStale issues:
project = ABC
AND status NOT IN (Done, Cancelled)
AND (assignee IS EMPTY OR updated <= -30d)
ORDER BY created ASCEpic progress:
"Epic Link" = ABC-123 ORDER BY status, rankComponent & Version Queries
Issues in component:
project = ABC AND component = "Frontend" AND status != DoneIssues without component:
project = ABC AND component IS EMPTY AND status != DoneTarget version:
fixVersion = "v2.0" ORDER BY status, priority DESCReleased versions:
fixVersion IN releasedVersions() ORDER BY fixVersion DESCLabel & Text Search Queries
Issues with label:
labels = urgent AND status != DoneMultiple labels (AND):
labels IN (frontend, bug) AND status != DoneSearch in summary:
summary ~ "authentication" ORDER BY created DESCSearch in summary and description:
text ~ "API integration" ORDER BY created DESCIssues with empty description:
description IS EMPTY AND issuetype = StoryPerformance-Optimized Queries
Good - Specific project first:
project = ABC AND status = "In Progress" AND assignee = currentUser()Bad - User filter first:
assignee = currentUser() AND status = "In Progress" AND project = ABCGood - Use functions:
sprint IN openSprints() AND status != DoneBad - Hardcoded sprint:
sprint = "Sprint 23" AND status != DoneGood - Specific date:
created >= 2026-01-01 AND created <= 2026-01-31Bad - Relative with high cost:
created >= -365d AND created <= -335dReporting Queries
Velocity calculation:
sprint = "Sprint 23" AND status = DoneThen sum story points
Bug rate:
project = ABC AND issuetype = Bug AND created >= startOfMonth()Average cycle time:
project = ABC AND resolved >= startOfMonth()
AND resolved <= endOfMonth()Calculate time from In Progress to Done
Stories delivered this quarter:
project = ABC AND issuetype = Story
AND resolved >= startOfYear() AND resolved <= endOfQuarter()Team capacity:
assignee IN membersOf("engineering-team")
AND sprint IN openSprints()Sum original estimates
Notification & Watching Queries
Issues I need to review:
status = "Pending Review" AND assignee = currentUser()Issues assigned to me, high priority:
assignee = currentUser() AND priority IN (Highest, High)
AND status != DoneIssues created by me, not resolved:
reporter = currentUser() AND status != Done
ORDER BY created DESCAdvanced Functions
Issues changed from status:
status WAS "In Progress" AND status = "Done"
AND status CHANGED AFTER startOfWeek()Assignee changed:
assignee CHANGED BY currentUser() AFTER -7dIssues re-opened:
status WAS Done AND status != Done ORDER BY updated DESCLinked issues:
issue IN linkedIssues("ABC-123") ORDER BY issuetypeParent epic:
parent = ABC-123 ORDER BY rankSaved Filter Examples
Daily Standup Filter:
assignee = currentUser() AND sprint IN openSprints()
AND status != Done ORDER BY priority DESCTeam Sprint Board Filter:
project = ABC AND sprint IN openSprints() ORDER BY rankBugs Dashboard Filter:
project = ABC AND issuetype = Bug AND status != Done
ORDER BY priority DESC, created ASCTech Debt Backlog:
project = ABC AND labels = tech-debt AND status = Backlog
ORDER BY priority DESCNeeds Triage:
project = ABC AND status = "To Triage"
AND created >= -7d ORDER BY created ASCJira Workflows Reference
Comprehensive guide to Jira workflow design, transitions, conditions, validators, and post-functions.
Default Workflows
Simplified Workflow
Open → In Progress → DoneSoftware 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 → DoneSupport Team:
New → Triaged → In Progress → Waiting on Customer → Resolved → ClosedDesign Team:
Backlog → Research → Design → Review → Approved → HandoffTransitions
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 NoneSubmit 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 eventConditions
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
#!/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())
#!/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())
Related skills
FAQ
What Jira automation patterns does jira-expert include?
Jira-expert includes auto-assignment by component lead, reporter reassignment on Waiting for Feedback, round-robin assignment for empty assignees, parent-subtask status synchronization, and Slack notification rules with documented triggers and conditions.
Does jira-expert run Jira automations automatically?
Jira-expert does not execute automations itself. The skill provides copy-ready Jira Automation rule patterns—triggers, conditions, and actions—that developers paste into Jira Cloud and adapt to their project.
Is Jira Expert safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.