---
name: jira-operations
description: Jira issue management for QUE project using Jira REST API. Use when searching issues, creating issues, updating issues, adding comments, transitioning issue status, changing an issue's type, or getting issue details. Automatically uses QUE project key.
allowed-tools: Bash, Read
---

# Jira Operations Skill

This Skill provides Jira issue management capabilities using the Jira REST API.

## Prerequisites

- Environment variables must be set:
  - `ATLASSIAN_SITE_NAME` - Jira site URL (e.g., https://mandeng.atlassian.net)
  - `ATLASSIAN_USER_EMAIL` - Your Jira email
  - `ATLASSIAN_API_TOKEN` - Your Jira API token
  - `JIRA_PROJECT_PREFIX` - Project key (e.g., QUE)
  - `JIRA_PROJECT_LABEL` - Project label automatically applied to all created issues (e.g., quber)
- `curl` and `jq` must be installed
- Default project: `QUE`

## Available Operations

### 1. Search Issues

**Script**: `scripts/search-issues.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/search-issues.sh \
  "JQL query" \
  [fields] \
  [limit]
```

**Arguments**:
- `jql`: JQL query string
- `fields`: Optional comma-separated field list (default: summary,status,issuetype,created,priority)
- `limit`: Optional max results (default: 50)

**Examples**:
```bash
# Search recent QUE issues
bash .claude/skills/jira-operations/scripts/search-issues.sh \
  "project = QUE ORDER BY created DESC" \
  "summary,status,assignee" \
  10

# Search by status
bash .claude/skills/jira-operations/scripts/search-issues.sh \
  "project = QUE AND status = 'In Progress'"
```

### 2. Get Issue Details

**Script**: `scripts/get-issue.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/get-issue.sh <issue-key>
```

**Arguments**:
- `issue-key`: Jira issue key (e.g., QUE-122)

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/get-issue.sh QUE-122
```

### 3. Create Issue

**Script**: `scripts/create-issue.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/create-issue.sh \
  "summary" \
  "description" \
  [issue-type] \
  [priority] \
  [project-key] \
  [parent-key] \
  [labels]
```

**Arguments**:
- `summary`: Issue title
- `description`: Issue description (markdown supported)
- `issue-type`: Optional (default: Task) - Task, Bug, Story, Epic, Subtask
- `priority`: Optional (default: Medium) - Highest, High, Medium, Low, Lowest
- `project-key`: Optional (default: QUE)
- `parent-key`: Optional - Parent issue key for subtasks or epic linking
- `labels`: Optional (default: $JIRA_PROJECT_LABEL) - Comma-separated labels (e.g., "quber,urgent")

**Examples**:
```bash
# Create standalone task
bash .claude/skills/jira-operations/scripts/create-issue.sh \
  "Add unit tests for spatial grid" \
  "## Description
Need comprehensive unit tests for the spatial grid module.

## Acceptance Criteria
- Test empty grid handling
- Test merged cell scenarios" \
  "Task" \
  "High"

# Create subtask under QUE-122
bash .claude/skills/jira-operations/scripts/create-issue.sh \
  "Test spatial grid edge cases" \
  "Verify edge case handling" \
  "Subtask" \
  "Medium" \
  "QUE" \
  "QUE-122"

# Create task linked to epic QUE-89
bash .claude/skills/jira-operations/scripts/create-issue.sh \
  "Implement feature X" \
  "Feature X implementation" \
  "Task" \
  "High" \
  "QUE" \
  "QUE-89"
```

### 4. Update Issue

**Script**: `scripts/update-issue.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/update-issue.sh \
  <issue-key> \
  <field> \
  <value>
```

**Arguments**:
- `issue-key`: Jira issue key
- `field`: Field to update (summary, description, priority, parent)
- `value`: New value

**Examples**:
```bash
# Update summary
bash .claude/skills/jira-operations/scripts/update-issue.sh \
  QUE-122 \
  summary \
  "Updated task summary"

# Link to epic QUE-89
bash .claude/skills/jira-operations/scripts/update-issue.sh \
  QUE-122 \
  parent \
  QUE-89

# Link subtask to parent task
bash .claude/skills/jira-operations/scripts/update-issue.sh \
  QUE-125 \
  parent \
  QUE-122
```

### 5. Add Comment

**Script**: `scripts/add-comment.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/add-comment.sh \
  <issue-key> \
  "comment text"
```

**Arguments**:
- `issue-key`: Jira issue key
- `comment`: Comment text (markdown supported)

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/add-comment.sh \
  QUE-122 \
  "Implementation completed in PR #45"
```

### 6. Transition Issue

**Script**: `scripts/transition-issue.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/transition-issue.sh \
  <issue-key> \
  <transition-id>
```

**Arguments**:
- `issue-key`: Jira issue key
- `transition-id`: Transition ID (get from get-transitions.sh)

**Example**:
```bash
# First get available transitions
bash .claude/skills/jira-operations/scripts/get-transitions.sh QUE-122

# Then transition
bash .claude/skills/jira-operations/scripts/transition-issue.sh QUE-122 31
```

### 7. Get Available Transitions

**Script**: `scripts/get-transitions.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/get-transitions.sh <issue-key>
```

**Arguments**:
- `issue-key`: Jira issue key

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/get-transitions.sh QUE-122
```

### 8. Delete Issue

**Script**: `scripts/delete-issue.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/delete-issue.sh <issue-key>
```

**Arguments**:
- `issue-key`: Jira issue key

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/delete-issue.sh QUE-123
```

**Warning**: This permanently deletes the issue. Use with caution.

### 9. Get Issue Types

**Script**: `scripts/get-issue-types.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/get-issue-types.sh [project-key]
```

**Arguments**:
- `project-key`: Optional (default: QUE)

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/get-issue-types.sh QUE
```

**Output**: Returns available issue types with their IDs, names, subtask flag, and hierarchy levels:
```json
[
  {"id": "10004", "name": "Epic", "subtask": false, "hierarchyLevel": 1},
  {"id": "10001", "name": "Task", "subtask": false, "hierarchyLevel": 0},
  {"id": "10002", "name": "Bug", "subtask": false, "hierarchyLevel": 0},
  {"id": "10003", "name": "Story", "subtask": false, "hierarchyLevel": 0},
  {"id": "10005", "name": "Subtask", "subtask": true, "hierarchyLevel": -1}
]
```

### 10. Add Attachment

**Script**: `scripts/add-attachment.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/add-attachment.sh \
  <issue-key> \
  <file> [file ...]
```

**Arguments**:
- `issue-key`: Jira issue key
- `file`: One or more local file paths to upload (each must exist)

**Examples**:
```bash
# Attach a single image
bash .claude/skills/jira-operations/scripts/add-attachment.sh \
  QUE-122 \
  output/diagram.png

# Attach multiple files in one call
bash .claude/skills/jira-operations/scripts/add-attachment.sh \
  QUE-122 \
  output/before.png \
  output/after.png
```

**Notes**:
- Uses the `/rest/api/2/issue/{key}/attachments` endpoint, which requires the
  `X-Atlassian-Token: no-check` header (handled by the script) and a
  `multipart/form-data` upload.
- Output is the JSON array of created attachment objects (filename, id, size).
- To display an attached image inline in a comment, upload it first, then
  reference it from `add-comment.sh` with Jira markup: `!filename.png!`.
  Comment markup only renders files that are already attached to the issue.

### 11. Delete Attachment

**Script**: `scripts/delete-attachment.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/delete-attachment.sh <attachment-id>
```

**Arguments**:
- `attachment-id`: Attachment ID, listed in `get-issue.sh` output under
  `fields.attachment[].id`

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/delete-attachment.sh 10231
```

**Notes**:
- The API takes the attachment ID, not the issue key and filename. Fetch the
  issue first to find the ID.
- Success is HTTP 204 with an empty body; the script checks for that and
  prints `{"success": true, ...}`.

**Warning**: This permanently removes the file from the issue. Re-uploading a
replacement creates a new attachment with a new ID, so any comment referencing
the old filename with `!filename.png!` markup stops rendering.

### 12. Update Comment

**Script**: `scripts/update-comment.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/update-comment.sh \
  <issue-key> \
  <comment-id> \
  "new body"
```

**Arguments**:
- `issue-key`: Jira issue key
- `comment-id`: Comment ID, listed in `get-issue.sh` output under
  `fields.comment.comments[].id`
- `new-body`: Replacement text. Markdown and Jira markup supported.

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/update-comment.sh \
  QUE-122 \
  15687 \
  "Corrected: the branch is feature/QUE-122-parser-fix at 05eefe7"
```

**Notes**:
- The body is replaced entirely, not appended to. Pass the full text you want
  the comment to end up with, including any part you are keeping.
- Output is the comment's `id`, `created`, and `updated` timestamps. A changed
  `updated` value confirms the write landed.
- Prefer `add-comment.sh` for anything that is genuinely new. Editing rewrites
  history, so use it to correct a comment that is now factually wrong, not to
  record a later development.

### 13. Link Issues

**Script**: `scripts/link-issues.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/link-issues.sh \
  <inward-issue-key> \
  <outward-issue-key> \
  [--type <link-type>]
```

**Arguments**:
- `inward-issue-key`: Jira issue key mapped to the API's `inwardIssue`
- `outward-issue-key`: Jira issue key mapped to the API's `outwardIssue`
- `--type`: Optional link type name (default: Relates)

**Examples**:
```bash
# Relate two issues (default type)
bash .claude/skills/jira-operations/scripts/link-issues.sh QUE-327 QUE-321

# Explicit link type
bash .claude/skills/jira-operations/scripts/link-issues.sh \
  QUE-327 \
  QUE-321 \
  --type "Relates"
```

**Notes**:
- Uses `POST /rest/api/2/issueLink`. Success is HTTP 201 with an empty body;
  the script checks for that and prints `{"success": true, ...}`.
- For the symmetric "Relates" type the argument order does not matter. For
  directional types, the two keys map to the API's `inwardIssue` and
  `outwardIssue` respectively, so check the type's inward/outward phrasing
  before choosing the order.
- Created links appear in `get-issue.sh` output under `fields.issuelinks`.

### 14. Delete Link

**Script**: `scripts/delete-link.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/delete-link.sh <link-id>
```

**Arguments**:
- `link-id`: Issue link ID, listed in `get-issue.sh` output under
  `fields.issuelinks[].id`

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/delete-link.sh 10681
```

**Notes**:
- Uses `DELETE /rest/api/2/issueLink/{linkId}`. Success is HTTP 204 with an
  empty body; the script checks for that and prints
  `{"success": true, "deleted_link": ..., "http_status": "204"}`.
- The API takes the link ID, not the two issue keys. Fetch either issue first
  to find it. The same link appears on both issues with the same ID.
- Use this to fix a directional link (for example `Duplicate`) that was created
  the wrong way round: delete it, then recreate with `link-issues.sh` in the
  correct order.

### 15. Download Attachment

**Script**: `scripts/download-attachment.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/download-attachment.sh \
  <attachment-id> \
  <output-path>
```

**Arguments**:
- `attachment-id`: Attachment ID, listed in `get-issue.sh` output under
  `fields.attachment[].id`
- `output-path`: Local path the file is written to

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/download-attachment.sh \
  10321 \
  /tmp/diagram.png
```

**Notes**:
- Uses `GET /rest/api/2/attachment/content/{id}`, which redirects to the media
  store; the script follows the redirect and writes the binary body verbatim.
- Success is HTTP 200; the script prints
  `{"success": true, "attachment_id": ..., "output_path": ..., "bytes": ...}`.
  On any other status it removes the partial file and exits 1.

### 16. Add Issue to Active Sprint

**Script**: `scripts/add-to-sprint.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/add-to-sprint.sh \
  <issue-key> \
  [project-key]
```

**Arguments**:
- `issue-key`: Jira issue key to move into the sprint
- `project-key`: Optional (default: `$JIRA_PROJECT_PREFIX`) - project whose board
  is searched for the active sprint

**Example**:
```bash
bash .claude/skills/jira-operations/scripts/add-to-sprint.sh QUE-374
```

Output:
```json
{
  "success": true,
  "issue": "QUE-374",
  "sprint_id": 708,
  "sprint_name": "2026 Sprint 22"
}
```

**Notes**:
- The sprint is resolved, not passed in. The script makes three calls: it reads
  `GET /rest/agile/1.0/board?projectKeyOrId={project}` and takes the **first**
  board in the response, then reads
  `GET /rest/agile/1.0/board/{boardId}/sprint?state=active` and takes the
  **first** active sprint on it, then posts `{"issues": ["KEY"]}` to
  `POST /rest/agile/1.0/sprint/{sprintId}/issue`.
- Because both lookups take the first element, a project with more than one
  board, or a board running more than one sprint at once, may resolve to a
  sprint other than the intended one. The resolved `sprint_id` and `sprint_name`
  are echoed in the output for exactly this reason - check them.
- It fails and exits 1 when the project has no board (`No board found for
  project ...`) and when the board has no sprint in the `active` state (`No
  active sprint found on board ...`). A sprint in `future` or `closed` state is
  never selected, so this cannot add to a sprint that has not started or has
  already ended.
- Success on the write is HTTP 204 with an empty body; any other status fails
  with the code in the message.
- There is no matching removal script. Taking an issue back out of a sprint is
  not wrapped.

### 17. Move Issues to Another Issue Type

**Script**: `scripts/move-issues.sh`

**Usage**:
```bash
bash .claude/skills/jira-operations/scripts/move-issues.sh \
  <issue-keys-csv> \
  <target-project> \
  <target-issuetype-id> \
  [target-parent-key]

bash .claude/skills/jira-operations/scripts/move-issues.sh task-status <task-id>
```

**Arguments**:
- `issue-keys-csv`: One or more issue keys, comma-separated (e.g. `QUE-342,QUE-343`)
- `target-project`: Project key the issues land in (e.g. `QUE`)
- `target-issuetype-id`: Numeric issue type ID, from `get-issue-types.sh`
- `target-parent-key`: Optional - parent the moved issues sit under in the
  target hierarchy
- `task-id`: For `task-status`, the move task ID printed by an earlier run

**Examples**:
```bash
# Convert a Task into an Epic
bash .claude/skills/jira-operations/scripts/move-issues.sh QUE-327 QUE 10004

# Convert two Subtasks into Stories under Epic QUE-327
bash .claude/skills/jira-operations/scripts/move-issues.sh \
  QUE-342,QUE-343 \
  QUE \
  10003 \
  QUE-327

# Check on a move that outlived the script's wait
bash .claude/skills/jira-operations/scripts/move-issues.sh task-status 10042
```

**Notes**:
- This is the only wrapped way to change an issue's hierarchy level
  (Subtask to Story, Task to Epic). `update-issue.sh` can change a parent but
  not a type.
- Uses `POST /rest/api/3/bulk/issues/move`, which is asynchronous. The script
  polls `GET /rest/api/3/task/{taskId}` every 2 seconds for up to 60 seconds and
  prints `{taskId, status, result}` on `COMPLETE`. On `FAILED`, `CANCELLED` or
  `DEAD` it prints the task and exits 1. If the task is still running at 60
  seconds it exits 1 with the task ID, and `task-status` reads it later.
- Statuses and fields are inferred: Jira carries each value over where the
  target type accepts it and applies the target's default where it does not.
  A move can therefore change an issue's status. Check the status of every
  moved issue afterwards, and treat a move as needing the same explicit ask as
  a transition.
- Notifications are off (`sendBulkNotification: false`).

## JQL Query Examples

**Recent issues**:
```
project = QUE ORDER BY created DESC
```

**By status**:
```
project = QUE AND status = 'In Progress'
```

**By assignee**:
```
project = QUE AND assignee = currentUser()
```

**Recently updated**:
```
project = QUE AND updated >= -7d
```

**By type**:
```
project = QUE AND issuetype = Task
```

**By priority**:
```
project = QUE AND priority = High
```

## Error Handling

All scripts return:
- **Exit code 0**: Success (JSON output)
- **Exit code 1**: Error (stderr contains error message)

## Extending This Skill

When an operation you need is not wrapped here, add a script rather than
working around its absence. The `jira-workflow` agent owns these scripts and is
the only one that writes them. The main agent does not author Jira scripts; if
it finds an operation missing, it delegates the work of adding it along with
the work of running it.

Adding a script grants a capability that `permissions.deny` in
`.claude/settings.json` would otherwise block, because the deny matches on the
`curl ...atlassian...` command prefix and these are invoked through `bash`.
That is the intended route, established when the deny and this Skill were
introduced together, so treat a new script as a deliberate widening of what
agents may do:

- **Surface every new script before writing it.** This includes read-only ones.
  Say which operation is missing, what the wrapper would call, and why the
  existing scripts do not cover it, then stop and wait for an answer. Never add
  one on your own judgment mid-task.
- Anything that writes to Jira needs agreement to that specific operation, not
  a general nod. Status transitions deserve particular care: the standing rule
  is that an issue's status never moves unless the user asked for it.
- If the answer is no, report what you could not determine and why, rather than
  reaching for an unwrapped call or inferring the result some other way.
- Follow the shape of the existing scripts: `set -e`, argument validation with
  a usage line on stderr, credentials read from the environment rather than
  hardcoded, and `jq '.'` on JSON output.
- `chmod +x` the file, run it once to confirm it works, and document it above
  with usage, arguments, and an example.

## Integration with Agents

This Skill is designed for use by the `jira-workflow` agent. When the agent needs to perform Jira operations:

1. Agent determines operation needed
2. Agent calls appropriate script via Bash tool
3. Script executes Jira REST API call via curl
4. Results returned as JSON to agent
5. Agent formats response for user

## Project Configuration

**Default Project**: QUE (Quberai)
**Site**: https://mandeng.atlassian.net

All scripts automatically use the QUE project key unless otherwise specified.
