From sdlc
Use acli (Atlassian CLI) to interact with Jira Cloud — create, search, view, link, and edit work items via shell commands instead of MCP tools. Prefer acli over Atlassian MCP to minimize token usage.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sdlc:acliThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use `acli` for all Jira operations. Never use Atlassian MCP tools when acli can do the same job.
Use acli for all Jira operations. Never use Atlassian MCP tools when acli can do the same job.
Before writing any description or comment to Jira, read .claude/refs/jira-adf.md.
acli --description-file passes the file contents directly to the Jira API. Plain markdown renders as raw symbols (e.g. **bold** appears literally). All descriptions must be Atlassian Document Format (ADF) JSON, saved to a .json temp file. The ref contains the full node reference, mark types, and ready-to-use Epic and Story templates.
MCP tool calls return full JSON payloads into context. acli runs in Bash, outputs only what you request, and exits. Token cost per operation: acli ≈ 10–50 tokens vs MCP ≈ 500–2000 tokens.
acli reads credentials from ~/.config/acli/. Before any acli command, run this auth guard:
# 1. DNS reachability check — fail fast if Atlassian is blocked
if ! getent hosts "$ATLASSIAN_SITE" >/dev/null 2>&1; then
echo "ERROR: $ATLASSIAN_SITE is DNS-blocked in this environment." >&2
echo "Fix: allowlist $ATLASSIAN_SITE and api.atlassian.com in GitHub → Settings → Copilot → Policies." >&2
exit 1
fi
# 2. Re-authenticate if no valid profile (Copilot agent sessions don't share ~/.config/acli from setup steps)
acli jira auth status 2>/dev/null || \
echo "$ATLASSIAN_API_TOKEN" | acli jira auth login \
--site "$ATLASSIAN_SITE" \
--email "$ATLASSIAN_EMAIL" \
--token
ATLASSIAN_API_TOKEN and ATLASSIAN_EMAIL are injected as environment variables by the Copilot agent runtime from repository Copilot secrets. If both are empty and auth fails, abort: "ATLASSIAN_API_TOKEN/ATLASSIAN_EMAIL not set — configure in GitHub → Settings → Secrets → Copilot."
ATLASSIAN_SITE must be set alongside ATLASSIAN_EMAIL / ATLASSIAN_API_TOKEN. Its value is the Jira site hostname from the consumer's project-context (e.g. your-org.atlassian.net).
acli jira project list --json 2>&1 | jq '.[].key'
acli jira workitem search \
--jql "project = CER AND issuetype = Epic AND summary ~ \"feature keyword\"" \
--fields "key,summary" --json 2>&1
acli jira workitem create \
--project "CER" \
--type "Story" \
--summary "Short action-oriented title" \
--description "As a fire safety engineer\nI want to...\nSo that..." \
--label "feature-slug" \
--parent "CER-123" \
--json 2>&1
Flags reference:
| Flag | Purpose |
|---|---|
-p, --project | Jira project key (e.g. CER) |
-t, --type | Work item type: Story, Task, Bug, Epic, Sub-task |
-s, --summary | Title (max 255 chars) |
-d, --description | Body text — use \n for newlines |
--description-file | Read description from a file (prefer for long ACs) |
--parent | Parent issue key — use for Epic linkage |
-l, --label | Comma-separated labels |
-a, --assignee | Email or @me |
--json | Output created issue as JSON (captures the new issue key) |
mkdir -p .tmp
bulk_file=$(mktemp ./.tmp/acli-bulk.XXXXXX)
trap 'rm -f "$bulk_file"' EXIT
# ... write JSON to $bulk_file, then:
acli jira workitem create-bulk --from-json "$bulk_file" 2>&1
JSON format for the bulk file:
[
{
"summary": "Story title",
"projectKey": "CER",
"issueType": "Story",
"description": "As a...\nI want to...\nSo that...",
"labels": ["feature-slug"],
"parentKey": "CER-123"
}
]
Generate a template:
acli jira workitem create --generate-json
Always extract all available context when using a ticket as a source. Use dedicated subcommands — not all data is embedded in the view response:
KEY="CER-456"
# Core fields (summary + description embedded in view)
issue=$(acli jira workitem view "$KEY" --json 2>&1)
summary=$(echo "$issue" | jq -r '.fields.summary')
description=$(echo "$issue" | jq -r '.fields.description // ""')
# All comments — use dedicated subcommand
comments=$(acli jira workitem comment list --key "$KEY" --json --paginate 2>&1 | \
jq -r '[.[].body] | join("\n---\n")' 2>/dev/null || echo "")
# Jira-to-Jira linked tickets only. NOTE: acli only exposes outwardIssueKey (= issue this one
# BLOCKS). "is blocked by" links show outwardIssueKey=null and the blocker key is NOT returned.
jira_links=$(acli jira workitem link list --key "$KEY" --json 2>&1 | \
jq -r '.issueLinks[]? | "\(.typeName): blocks \(.outwardIssueKey // "(blocked-by upstream — key not exposed)")"' 2>/dev/null || echo "")
# Attachments — list only (filename + MIME type + URL), no download via acli
attachments=$(acli jira workitem attachment list --key "$KEY" --json 2>&1 | \
jq -r '[.[] | "\(.filename) [\(.mimeType)]: \(.content)"] | join("\n")' 2>/dev/null || echo "")
echo "=== SUMMARY ===" && echo "$summary"
echo "=== DESCRIPTION ===" && echo "$description"
echo "=== COMMENTS ===" && echo "$comments"
echo "=== LINKED JIRA TICKETS ===" && echo "$jira_links"
echo "=== ATTACHMENTS ===" && echo "$attachments"
Known limitations:
acli jira workitem remote-links does not exist — external/remote links (Confluence, Figma, Slack URLs) are NOT accessible via acli. Use the Jira REST API if you need them.content URL in the attachment list requires Jira auth headers that acli does not expose.acli auth token does not exist.Raw JSON (when you need to parse specific fields):
acli jira workitem view CER-456 --json 2>&1
acli jira workitem edit CER-456 \
--summary "Updated title" \
--description "Updated description" 2>&1
acli jira workitem link type 2>&1 # available link type names (e.g. Blocks, Relates, Cloners)
Use the link create form with --out/--in — NOT the positional link A B form (unreliable).
⚠️ acli direction is counter-intuitive (verified against the Jira UI): --in is the BLOCKER, --out is the BLOCKED issue. To express "A blocks B" (A is the prerequisite that B depends on):
# A blocks B → --out <B, blocked/downstream> --in <A, blocker/prerequisite>
acli jira workitem link create --out CER-102 --in CER-101 --type Blocks --yes 2>&1
Reading links back — acli jira workitem link list --key <KEY> --json returns
{ "issueLinks": [ { "id", "typeName", "outwardIssueKey" } ] }:
outwardIssueKey (non-null) = an issue this story blocks (downstream).outwardIssueKey = an "is blocked by" (upstream) link — acli does NOT expose the blocker's key.<KEY>, invert: a sibling S blocks <KEY> iff S's link list has outwardIssueKey == <KEY>. Or read the Jira UI's "is blocked by" section.# Delete a link by its id (from link list)
acli jira workitem link delete --id <LINK-ID> --yes 2>&1
# Stories in a project
acli jira workitem search \
--jql "project = CER AND issuetype = Story AND labels = \"feature-slug\"" \
--fields "key,summary,status" \
--json 2>&1
# Count only
acli jira workitem search \
--jql "project = CER AND issuetype = Story" \
--count 2>&1
acli jira workitem comment add CER-456 --body "Spec ready: docs/superpowers/specs/..." 2>&1
Always follow this order to avoid creating orphaned stories:
1. Search for existing Epic matching feature area
2. Write all story descriptions to mktemp files (avoids shell escaping issues AND collisions)
3. Use create-bulk with --from-json for efficiency
4. Capture returned issue keys
5. Link blocking dependencies between stories
6. Comment on each story with the feature doc path
7. Temp files are auto-cleaned by the EXIT trap
Description text often contains special characters. Use files instead of inline strings.
Always use mktemp for temp files — write them under the project's ./.tmp/ (inside
the permission scope, gitignored), never /tmp (prompts on every access). This
guarantees a unique path and pairs with a trap for automatic cleanup:
# Create a unique temp file in ./.tmp; clean it up automatically on exit
mkdir -p .tmp
desc_file=$(mktemp ./.tmp/acli-desc.XXXXXX) # X's at end (macOS), no .txt suffix
trap 'rm -f "$desc_file"' EXIT
cat > "$desc_file" << 'EOF'
As a fire safety engineer
I want to view a list of penetrations by room
So that I can track certification status without opening each record
**Acceptance Criteria**
- [ ] List shows penetration ID, type, status, and last inspection date
- [ ] List filters by status (certified / pending / failed)
- [ ] Empty state shown when no penetrations exist
**Out of Scope**
- Editing penetrations from this view
EOF
acli jira workitem create \
--project "CER" \
--type "Story" \
--summary "View penetrations list by room" \
--description-file "$desc_file" \
--parent "CER-123" \
--json 2>&1
mktempreturns a path like./.tmp/acli-desc.A3f9kB— unique per invocation, never collides with parallel runs.trap ... EXITfires on normal exit, errors, and interrupts.
result=$(acli jira workitem create --project CER --type Story --summary "Title" --json 2>&1)
key=$(echo "$result" | jq -r '.key')
echo "Created: $key"
getent hosts "$ATLASSIAN_SITE" fails) → abort immediately with the DNS-block error message above; do NOT attempt auth or any other acli commandacli jira project list to confirm keyacli jira workitem view <key>sleep 1 between bulk operationsnpx claudepluginhub whimzylive/nightshift-ai --plugin sdlcCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.