Manages Jira issues via the Atlassian MCP with a project metadata cache that eliminates repeated discovery calls. Caches custom fields, workflow graphs, transition requirements, and user mappings on first use — keeping most operations to a single MCP call. Supports per-issue-type description templates (customizable per project) for consistent, well-structured issue content.
/jira-sdlc [--project <KEY>] [--force-refresh] [--init-templates] [--site <host>] [--skip-workflow-discovery]
| Flag | Description | Default |
|---|---|---|
--project <KEY> |
Jira project key to use (e.g., PROJ). Auto-detected from git branch or .sdlc/config.json → jira.defaultProject. When jira.projects is set (≥2 entries), values outside the list are rejected. |
Auto |
--force-refresh |
Rebuild the project cache from scratch (cache is permanent by default; use when project metadata has changed) | — |
--init-templates |
Copy the skill's default issue type templates to .claude/jira-templates/ for customization |
— |
--site <host> |
Sanitized site host (lowercased, . → _, e.g., acme_atlassian_net). Disambiguates --check/--load when the same project key is cached under multiple site subdirectories. |
Unset |
--skip-workflow-discovery |
Bypass Phase 5; cache marks each non-subtask issue type { unsampled: true }. Transitions fall back to a live getTransitionsForJiraIssue per issue. Use in CI and other pre-seeded environments. |
false |
/jira-sdlc
Create a bug for the login page redirect issue on Firefox with SSO users. High priority, assign to Jane.
Creates PROJ-147 with a structured description using the Bug template.
/jira-sdlc
Move PROJ-147 to Done
Transitions the issue using the cached transition ID, automatically including required fields (e.g., resolution).
/jira-sdlc
Find all open high-priority bugs assigned to me in project PROJ
Returns a table of matching issues.
/jira-sdlc
Add a comment to PROJ-147 with the root cause analysis
Converts the markdown to Atlassian Document Format (ADF) via markdown-to-adf.js and posts the comment. When the tool input declares contentFormat='adf', placeholder detection (C13) walks the parsed ADF tree and applies the regex only to text node values — bracket characters in JSON-serialized ADF (e.g., [null,...] from a stringified array) never trip the bracket-form regex.
/jira-sdlc
Set PROJ-147 story points to 5 and add the label "backend"
Updates both fields in a single editJiraIssue call.
/jira-sdlc --project PROJ --force-refresh
Runs the 5-phase initialization, reports N issue types and M workflow states mapped.
/jira-sdlc --project PROJ --init-templates
Copies default templates for each issue type to .claude/jira-templates/. Edit them to match your team's conventions.
/jira-sdlc --project BACKEND
Create a story for the token refresh feature
Creates the story in the BACKEND project using the Story template.
Default templates ship in plugins/sdlc-utilities/skills/jira-sdlc/templates/ — one file per issue type:
Bug.mdStory.mdTask.mdEpic.mdSub-task.mdSpike.md
Project-level overrides live at .claude/jira-templates/<IssueTypeName>.md. File names must match Jira issue type names exactly (case-sensitive).
Resolution order:
- Project custom —
.claude/jira-templates/<IssueTypeName>.md - Skill default —
plugins/sdlc-utilities/skills/jira-sdlc/templates/<IssueTypeName>.md - Subtask fallback — when none of the above exist, the prepare script consults a fallback map for subtask variants (see below).
- No template — when no fallback applies, the skill emits a warning and aborts the operation.
Run /jira-sdlc --init-templates to export the skill defaults to .claude/jira-templates/ as a starting point, then edit them to match your team's conventions.
When an issue type lacks both a custom and a shipped template, the prepare script consults a closed fallback map before resolving to none:
| Issue type | Falls back to |
|---|---|
Sub-bug |
Bug |
Sub-task |
Task |
Subtask |
Task |
When a fallback is applied the skill prints a one-line notice:
Using <Parent> template for <Type> — override at .claude/jira-templates/<Type>.md.
Override the fallback by creating a custom template at the path shown.
When no fallback applies (e.g., a fictional Whim type with no shipped template) the skill prints a warning and stops:
No template for <Type>. Run /jira-sdlc --init-templates or create .claude/jira-templates/<Type>.md.
Tip: On non-English Jira instances,
--init-templatesdetects unmapped issue types and interactively asks which default template to use for each.
When Jira uses a non-English language, issue type names are localized (e.g., "Zadanie" for Task in Polish). Running --init-templates detects unmapped types and interactively asks which default template to use for each, with suggestions based on the Jira hierarchy level (Epic for top-level types, Task for standard types).
After interactive setup, template files are created with your locale's names (e.g., .claude/jira-templates/Zadanie.md) containing the selected template content as a starting point. Edit them to match your team's conventions.
Example custom Bug template (.claude/jira-templates/Bug.md):
## Summary
[One sentence: what is broken and where]
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
[What should happen]
## Actual Behavior
[What actually happens]
## Environment
- Browser / OS:
- Version / build:
## Root Cause (if known)
[Leave blank if unknown]The cache is stored at ~/.sdlc-cache/jira/<sanitizedSiteHost>/<PROJECT_KEY>.json and is permanent by default — it does not expire on a timer. sanitizedSiteHost is the site URL host lowercased with . replaced by _ (e.g., acme.atlassian.net → acme_atlassian_net). The cache lives outside the working tree so it is never committed, survives repo clones, and supports repos that map to multiple Jira tenants (one subdirectory per site). Refreshed when --force-refresh is passed or when an operation fails due to stale cached data (e.g., invalid transition IDs or changed field schemas), triggering an automatic rebuild and retry.
The cache contains:
cloudId— Atlassian cloud instance identifiersiteUrl— canonical form is the full origin URL (e.g.,https://acme.atlassian.net); the prepare script also accepts a bare host (e.g.,acme.atlassian.net) and will strip any trailing path. ThesanitizedSiteHostsubdirectory is always derived from the host portion only.- Issue types and their field schemas (including custom fields)
- Workflow graphs with transition IDs and per-transition required fields (or
{ unsampled: true }when Phase 5 was skipped) - Available issue link types
- User account ID mappings (display name → accountId)
After initialization, most operations require a single MCP call instead of 4–8 discovery calls, significantly reducing latency and token usage.
When an Atlassian MCP call returns a cloudId authorization error (response text matches isn't explicitly granted or HTTP 401/403 with the cloudId in the message), the skill follows this ladder once (spec R23):
- Call
getAccessibleAtlassianResourcesexactly once. - Compare the returned cloudId(s) against the cached value at
~/.sdlc-cache/jira/<site>/<KEY>.json. - If different, run
/jira-sdlc --force-refreshand reload the cache. - Retry the original MCP call exactly once. If it still fails with the same error, the skill surfaces the error and stops — it does not loop.
Earlier versions of this skill stored the cache in-repo at .sdlc/jira-cache/<KEY>.json (and before that, .claude/jira-cache/<KEY>.json). On the next --check, the prepare script detects either legacy location, copies the file to the home layout using the siteUrl embedded in the JSON, and emits a warning. The legacy file is left in place for the user to clean up once confident. Migration is idempotent: subsequent runs find the home cache first and skip the legacy probe entirely.
Repos that map to multiple Jira projects can enumerate the allowed keys in .sdlc/config.json:
{
"jira": {
"defaultProject": "FOO",
"projects": ["FOO", "BAR", "BAZ"]
}
}When jira.projects is set with two or more entries:
--project <KEY>arguments are validated against the list. Values outside the list are rejected (prepare script exits 1).- When branch parsing,
defaultProject, and--projectall fail to resolve a key, the skill presents a closed-list AskUserQuestion restricted to the configured projects — no free-form input. - Single-project repos (no
jira.projects, or only one entry) retain the prior four-step fallback with a free-form prompt as the final step.
Phase 5 workflow discovery fires an MCP call per non-subtask issue type (and several per type for transition sampling). In pre-seeded CI environments where the cache is bootstrapped ahead of time, or on cold runs where end-to-end latency matters more than transition coverage, pass --skip-workflow-discovery:
/jira-sdlc --project PROJ --force-refresh --skip-workflow-discoveryThe resulting cache stores workflows: { "<Type>": { "unsampled": true } } for every non-subtask issue type. At runtime, any transition operation that encounters an unsampled marker routes through a live getTransitionsForJiraIssue per issue — the same auto-refresh path used when a cached transition ID stales out. Other cache sections (issue types, field schemas, link types, user mappings) are still populated normally.
- Atlassian MCP — must be configured and connected (
mcp__atlassian__*tools available in your Claude Code session) - Jira project access — the authenticated user must have read/write permission on the target project
- No additional CLI tools required
When both mcp__atlassian__ and mcp__claude_ai_Atlassian__ are registered in the deferred-tools list, the skill auto-falls-back from the primary mcp__atlassian__ namespace to mcp__claude_ai_Atlassian__ once when the primary returns a cloudId authorization error. The working namespace is persisted for the rest of the session — no per-call probing (spec R23).
| Field | Value |
|---|---|
argument-hint |
[--project <KEY>] [--force-refresh] [--init-templates] [--site <host>] [--skip-workflow-discovery] |
| Plan mode | Not adapted (writes to Jira) |
Every write operation (create, edit, transition, comment, link, assign, worklog, bulk) passes through a two-step gate before any MCP call is dispatched. Read operations (search, view) skip this gate entirely.
Before presenting a proposal, the skill runs an internal critique against the assembled payload:
- Template completeness — every
##heading indescriptionmust come from the resolved template; no invented sections. - Placeholder resolution —
[bracketed prose]and{name}markers flagged aslow-confidence must be resolved viaAskUserQuestionbefore the payload is shown (R19). High-confidence auto-fills are surfaced as findings but do not block. - Field validity — required fields for the selected transition are present; field values are within cache-validated enumerations.
The findings are surfaced as a three-line block before the approval prompt:
Initial: <one-line summary of the initial draft>
Critique: <findings, or "none">
Final: <one-line summary of the revised payload>
The critique artifact is written to $TMPDIR/jira-sdlc/critique-<hash>.json.
After the critique block, the full final payload (the exact bytes the MCP call will dispatch) is printed. The user must respond with one of:
- approve — proceed to dispatch
- change
<what>— describe the desired change; the skill loops back through the critique pass with a revised draft and new artifacts - cancel — abort without dispatching
On approve, an approval token is written to $TMPDIR/jira-sdlc/approval-<hash>.token. No write MCP call is dispatched without this token.
A PreToolUse hook (hooks/pre-tool-jira-write-guard.js) re-derives the payload hash from the tool input at dispatch time, verifies both artifacts exist and are under 10 minutes old, and blocks dispatch if either check fails. If dispatch is blocked, the hook's permissionDecisionReason is surfaced verbatim — the skill does not retry by guessing what changed.
Canonicalization strips null-valued keys recursively (in addition to undefined) so that MCP-harness defaulting (e.g., schema-default commentVisibility: null injection between artifact write and hook read) cannot desync the skill-side and hook-side hashes.
When SDLC_DEBUG_DUMP=1 is set, the hook writes the received tool_input and the canonical-JSON byte-string to $TMPDIR/jira-sdlc-debug/<hook-hash-prefix>.json. Use this to diff against the skill artifact ($TMPDIR/jira-sdlc/critique-<hash>.json) byte-for-byte when a hash mismatch denial occurs. The dump is diagnostic only — it does not change the deny decision.
| File / Artifact | Description |
|---|---|
~/.sdlc-cache/jira/<site>/<KEY>.json |
Project metadata cache (home-keyed, outside the working tree): cloudId, issue types, field schemas, workflows, user mappings |
.claude/jira-templates/<Type>.md |
Project-level issue description templates (created only when --init-templates is run, or manually) |
$TMPDIR/jira-sdlc/critique-<hash>.json |
Per-operation critique artifact (write-ops only); contains initial/findings/final summary; verified by the PreToolUse hook |
$TMPDIR/jira-sdlc/approval-<hash>.token |
Per-operation approval token (write-ops only); created on approve; verified and deleted by the PreToolUse hook after dispatch |
| Jira issues | Created or updated via the Atlassian MCP |
Before any createJiraIssue, editJiraIssue, or addCommentToJiraIssue MCP call, the skill pipes the description / comment body through scripts/skill/jira.js --validate-body (which delegates to scripts/lib/links.js). The Jira site (jiraSite) is resolved deterministically from the cached ~/.sdlc-cache/jira/<site>/<KEY>.json — the skill never constructs the validator context.
URL classes checked:
| Class | Check | Failure code |
|---|---|---|
GitHub github.com/<owner>/<repo>/(issues|pull)/<n> |
Owner/repo identity matches the current git remote origin; issue/PR number exists on that repo |
github-context-mismatch, github-not-found |
Atlassian *.atlassian.net/browse/<KEY-N> |
Host matches the cached siteUrl for the active project |
atlassian-site-mismatch, atlassian-site-ambiguous |
Generic http(s)://... |
HEAD reachable (falls back to GET on 405), 5s timeout | url-not-found, url-server-error, url-unreachable |
Hosts in the built-in skip list (linkedin.com, x.com, twitter.com, medium.com) are reported as skipped, not violations. Set SDLC_LINKS_OFFLINE=1 to skip generic reachability checks while keeping context-aware checks (GitHub identity, Atlassian host). On non-zero exit, the MCP write call is not dispatched and the payload is never sent to Jira. No flag toggles this gate — it is hard.
/plan-sdlc— write an implementation plan from a Jira ticket/execute-plan-sdlc— execute an existing plan