Jira¶
Client¶
client
¶
Jira client with acli-first delegation and REST API fallback.
Provides a JiraClient class that delegates to the Atlassian CLI
(acli) for operations it supports and falls back to the REST API
for gaps (changelog queries, visibility-restricted comments, custom
fields, attachment upload, ADF conversion).
When acli is not on PATH, all operations use the REST API.
Usage::
from agentic_ci.jira import JiraClient
client = JiraClient.from_env(url="https://myorg.atlassian.net")
ticket = client.get_issue("PROJ-123")
client.add_comment("PROJ-123", "Fixed in PR #42")
JiraError(message, status_code=None, response_text='')
¶
JiraClient(url, email, token, *, timeout=30)
¶
Jira client that delegates to acli where possible.
On init, checks for acli on PATH. If available, write
operations (create, edit, transition, assign, comment, link)
and search/view use acli subprocess calls. Read operations
that need ADF conversion, changelog queries, custom fields,
or visibility-restricted comments fall back to the REST API.
Source code in src/agentic_ci/jira/client.py
from_env(url=None)
classmethod
¶
Create a client from environment variables.
Reads JIRA_URL, JIRA_EMAIL (or JIRA_USER), and
JIRA_API_TOKEN.
Raises RuntimeError if required variables are missing.
Source code in src/agentic_ci/jira/client.py
get_issue(key)
¶
Fetch a single issue with comments. Returns a normalised dict.
The returned dict has keys: key, summary, description
(plain text), created (ISO 8601 timestamp), issue_type,
labels, status, project, components, reporter_name,
reporter_email, comments.
Source code in src/agentic_ci/jira/client.py
search(jql, *, max_results=500, fields=None)
¶
Search issues by JQL. Returns normalised dicts with created timestamps.
fields overrides the Jira fields requested for each issue. By
default the fields needed for every normalised key are requested
(summary, description, created, issuetype, labels, comment, status).
The normalised dict has the same keys either way; values for fields
that were not requested, or that Jira omitted, fall back to empty
defaults. When fields only names key and/or id, pages of
up to 5000 issues are requested (the enhanced search maximum for
key-only listings); otherwise pages hold 50 issues. A single field
name may be passed as a string. Use :meth:search_keys to list keys
only.
Source code in src/agentic_ci/jira/client.py
search_keys(jql, *, max_results=5000)
¶
Return the keys of issues matching jql, in Jira's order.
Requests only the issue key (Jira always adds the id), so each
request can return up to 5000 issues, the enhanced search maximum.
Follows nextPageToken until max_results keys or the last
page. Issues without a key are skipped.
Source code in src/agentic_ci/jira/client.py
get_label_author(key, label)
¶
Find who most recently added a label via the changelog.
Returns {"found": True, "email": ..., "displayName": ...,
"added_at": ...} or {"found": False}.
added_at is a timezone-aware UTC ISO-8601 timestamp from the
most recent matching label-addition event, or None when the
timestamp is unavailable or unparseable (including the reporter
fallback path).
Falls back to the ticket reporter if the label was set at creation time (no changelog entry).
Source code in src/agentic_ci/jira/client.py
get_description_editors(key)
¶
Return email addresses of all users who edited the issue description.
Walks the full changelog looking for description field changes. Returns a list of unique email addresses (may be empty if the description was never edited after creation).
Source code in src/agentic_ci/jira/client.py
get_custom_field(key, *field_names)
¶
Read custom fields by name. Returns {name: value}.
Source code in src/agentic_ci/jira/client.py
search_parent_epics(jql)
¶
Find parent Epic keys for issues matching a child JQL query.
Source code in src/agentic_ci/jira/client.py
get_issue_links(key)
¶
Return an issue's links as a normalised list.
Each entry describes one link and the issue on the other end:
type: the link type name (e.g."Blocks").direction:"inward"or"outward"-- which side the linked issue sits on. For a"Blocks"link,"inward"means this issue is blocked by the linked issue;"outward"means this issue blocks it.key: the linked issue key (e.g."PROJ-123").status: the linked issue's status name (e.g."Open"), or""if unavailable.
Returns an empty list if the issue has no links.
Source code in src/agentic_ci/jira/client.py
add_comment(key, body, *, visibility_group=None)
¶
Post a comment, optionally restricted to a visibility group.
The body is plain text (with optional markdown markup, converted
to ADF for the REST API). Always uses the REST API because acli
comment create does not support --body-adf. Returns True
on success, False on failure.
Source code in src/agentic_ci/jira/client.py
update_comment(key, comment_id, body, *, visibility_group=None)
¶
Update an existing comment by ID.
Same ADF conversion and visibility semantics as add_comment.
Returns True on success, False on failure.
Source code in src/agentic_ci/jira/client.py
edit_labels(key, *, add=None, remove=None)
¶
Add and/or remove labels on an issue (atomic update).
Delegates to acli for add-only operations. Falls back to
REST API for remove or mixed add+remove (acli edit --labels
replaces; the REST API supports atomic add/remove).
Source code in src/agentic_ci/jira/client.py
transition(key, status)
¶
Transition an issue to a new status by name.
Source code in src/agentic_ci/jira/client.py
assign(key, assignee)
¶
Assign an issue to a user by account ID or email.
Source code in src/agentic_ci/jira/client.py
create_issue(project, issue_type, summary, *, description='', parent_epic='', **extra_fields)
¶
Create a new issue. Returns the issue key.
Uses acli for simple creates (no extra_fields). Falls back to REST API when Epic Link or extra fields are needed.
Source code in src/agentic_ci/jira/client.py
link_issues(source, target, link_type)
¶
Link two issues with a named link type.
Source code in src/agentic_ci/jira/client.py
961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 | |
attach_file(key, filepath)
¶
Attach a file to an issue.
Source code in src/agentic_ci/jira/client.py
set_security_level(key, level_name)
¶
Set the security level on an issue by level name.
Fetches available security levels for the issue, matches level_name (case-insensitive), and sets it via the REST API.
Raises JiraError if the level name is not found.
Source code in src/agentic_ci/jira/client.py
set_custom_field(key, field_name, value)
¶
Set a custom field by name.
Consults the field schema to determine value format: text/string
fields get the raw string, option/select fields get {"value": v}.
If the value is valid JSON, it is sent as-is.
Source code in src/agentic_ci/jira/client.py
ADF Conversion¶
adf
¶
Atlassian Document Format (ADF) conversion utilities.
Converts between plain text (with markdown markup) and ADF, the JSON document format used by Jira Cloud REST API v3 for rich-text fields.
text_to_adf(text)
¶
Convert plain text with markdown markup to Atlassian Document Format.
Handles:
- lang ... fenced code blocks -> codeBlock nodes
- {expand:Title}...{expand} -> expand nodes (collapsible sections)
- ---- or --- on a line by itself -> rule nodes (horizontal dividers)
- # through ###### headings -> heading nodes
- - bullets -> bulletList nodes
- bold and italic inline markup
- URLs -> inlineCard nodes
- Double newlines split paragraphs, single newlines become hardBreak
Source code in src/agentic_ci/jira/adf.py
adf_to_text(adf)
¶
Extract plain text from an ADF document.
Source code in src/agentic_ci/jira/adf.py
acli Wrapper¶
acli
¶
Atlassian CLI (acli) wrapper for agentic-ci.
Downloads the acli binary if not already on PATH, handles authentication, and provides a subprocess runner for acli commands.
AcliError(message, returncode=1, stderr='')
¶
is_available()
¶
ensure_acli(dest='/usr/local/bin/acli')
¶
Download acli if not already on PATH. Returns absolute path to binary.
Source code in src/agentic_ci/jira/acli.py
setup_auth(site=DEFAULT_SITE)
¶
Authenticate acli using JIRA_EMAIL + JIRA_API_TOKEN env vars.
Source code in src/agentic_ci/jira/acli.py
run_acli(*args, json_output=False, check=True)
¶
Run an acli command.
If json_output is True, appends --json to the command.
If check is True, raises AcliError on non-zero exit.