Jira Publish
Publish a Jira draft file to create issues, update descriptions, or add comments.
CRITICAL: Preview Before All Mutations
NEVER execute without showing the user a preview first.
For every publish operation:
- •Read the draft file
- •Validate frontmatter completeness
- •Show preview with exact content
- •Ask for approval explicitly
- •Only then execute the command
- •Update draft file with published status
If the user hasn't approved, DO NOT execute.
Workflow
- •Read draft file from provided path (use Read tool — do NOT extract body with shell)
- •Parse frontmatter fields from the Read output (the YAML between
---delimiters) - •Validate required fields for action type
- •Resolve assignee - if
assigneeis an email, look up account ID vialookupJiraAccountId - •Resolve mentions - look up any
@emailmentions in body (see Mentions section) - •Show preview in box format (including resolved assignee and mentions)
- •Ask for explicit approval
- •Execute via jira-api.sh with all options (--assignee, --component, --parent)
- •Add links if any specified
- •Update draft with
status: publishedandpublished_issue - •Return issue URL
Input
User provides path to a draft file:
/dataops-assistant:jira-publish .jira-drafts/20250203-143022-openai-instance.md
Preview Format
Create Issue
┌─────────────────────────────────────────────────────────┐ │ PREVIEW: Create Issue │ ├─────────────────────────────────────────────────────────┤ │ Project: PE │ │ Type: Story │ │ Summary: Set up dedicated OpenAI instance for CMS │ │ Assignee: john@company.com -> John Smith (5b10ac...) │ # if present │ Parent: PE-100 │ # if present │ Components: Backend, API │ # if present │ │ │ Description (first 500 chars): │ │ ───────────────────────────── │ │ :::context │ │ Forever ago, we set up the initial OpenAI instance... │ │ ::: │ │ │ │ ## What We Have Today │ │ - Single OpenAI instance shared across all... │ │ │ │ Links to add after creation: │ │ - blocks PE-456 │ │ - relates-to MAT-28 │ └─────────────────────────────────────────────────────────┘
Update Issue
┌─────────────────────────────────────────────────────────┐
│ PREVIEW: Update Issue │
├─────────────────────────────────────────────────────────┤
│ Target: PE-1234 │
│ Fix Version: 2025.1.0 │ # if present
│ │
│ New Description (first 500 chars): │
│ ───────────────────────────────── │
│ {status:UPDATE 2025-02-03:purple} │
│ │
│ ## Investigation Findings │
│ Checked Nolio server logs and found... │
└─────────────────────────────────────────────────────────┘
Add Comment
┌─────────────────────────────────────────────────────────┐ │ PREVIEW: Add Comment │ ├─────────────────────────────────────────────────────────┤ │ Target: PE-1234 │ │ Fix Version: llm_utils#0.6.4 │ # if present │ │ │ Comment: │ │ ──────── │ │ What's the expected timeline for getting the log │ │ forwarder config approved? │ │ │ │ We have a dependency on this for Q2 monitoring. │ └─────────────────────────────────────────────────────────┘
Commands
CRITICAL: Always use the bin wrapper. Never call scripts by direct path.
The bin wrapper delegates to the run wrapper which sources DATAOPS_ASSISTANT_ROOT and resolves
paths — without it, scripts cannot find their sibling files (like md-to-adf.js).
# CORRECT — single unambiguous path JIRA_API="$HOME/.dataops-assistant/bin/jira-api.sh" # WRONG — do NOT use direct paths to scripts: # JIRA_API=~/.dataops-assistant/skills/jira-publish/scripts/jira-api.sh ← BROKEN # JIRA_API=/Users/.../incubating-dataops-assistant-plugin/skills/... ← BROKEN
Create Issue
# Basic create echo 'markdown content' | bash "$JIRA_API" create <project> "<issue_type>" "<summary>" - --md # With optional fields (assignee must be account ID, not email) echo 'markdown content' | bash "$JIRA_API" create <project> "<issue_type>" "<summary>" - --md \ --assignee "<account_id>" \ --parent "<epic_key>" \ --component "Backend" --component "API"
Update Issue
# Update description only echo 'markdown content' | bash "$JIRA_API" update <issue_key> - --md # Update description and set fix version echo 'markdown content' | bash "$JIRA_API" update <issue_key> - --md \ --fix-version "2025.1.0"
Add Comment
# Add comment only echo 'markdown content' | bash "$JIRA_API" comment <issue_key> - --md # Add comment AND set fix version (two API calls handled internally) echo 'markdown content' | bash "$JIRA_API" comment <issue_key> - --md \ --fix-version "llm_utils#0.6.4"
Add Links (after create)
For each link in frontmatter, use the exact type specified:
# Frontmatter format:
links:
- type: blocks # Use "blocks" in command
target: PE-456
- type: is-blocked-by # Use "is-blocked-by" in command
target: PE-789
# Execute for EACH link, using the type FROM the frontmatter: bash "$JIRA_API" link <created_issue> <type_from_frontmatter> <target_from_frontmatter> # Examples: bash "$JIRA_API" link PE-1234 blocks PE-456 bash "$JIRA_API" link PE-1234 is-blocked-by PE-789
CRITICAL: Do NOT default to relates-to. Use exactly what the frontmatter specifies.
Valid link types:
- •Standard (all Jira):
blocks,is-blocked-by,relates-to,duplicates,clones,is-cloned-by - •Extended (Cloud only):
causes,is-caused-by
Note: The 4 core types (Blocks, Relates, Duplicate, Cloners) work in all Jira versions. The Causes type is a Jira Cloud extended feature. Other Cloud extended types (implements, reviews, product discovery types) and custom link types are not supported.
Validation Before Preview
Create Action
- •
projectmust be present - •
issue_typemust be valid (Story, Task, Bug, Epic, Sub-task) - •
summarymust be present
Update Action
- •
target_issuemust be present and valid format (PROJ-123) - •
fix_version(optional) - version name must exist in the project
Comment Action
- •
target_issuemust be present and valid format (PROJ-123) - •
fix_version(optional) - version name must exist in the project
If validation fails, show error and stop:
❌ Cannot publish: Missing required field 'project' for create action.
Assignee Resolution
CRITICAL: Email Domain is @mcg.com
The company email domain is @mcg.com, NOT @mcghealth.com. Do not be confused by the Atlassian cloud ID being mcghealth - that's just the subdomain for Atlassian Cloud, not the email domain.
- •✅ Correct:
jack.stouffer@mcg.com - •❌ Wrong:
jack.stouffer@mcghealth.com
If assignee is present in frontmatter and looks like an email (contains @), resolve it to an account ID:
1. Call: mcp__atlassian__lookupJiraAccountId(query: "user@company.com") 2. Extract accountId from result 3. If no result found, abort: ❌ Cannot publish: Could not resolve assignee "user@company.com" Check spelling or use valid Atlassian email address. 4. Store resolved ID to pass to jira-api.sh --assignee
Preview with Assignee
┌─────────────────────────────────────────────────────────┐ │ PREVIEW: Create Issue │ ├─────────────────────────────────────────────────────────┤ │ Project: PE │ │ Type: Story │ │ Summary: Set up dedicated OpenAI instance │ │ Assignee: user@company.com -> John Smith (5b10ac8d...) │ │ Parent: PE-100 │ │ Components: Backend, API │
Mentions (@user)
Remember: Email domain is @mcg.com (not @mcghealth.com)
The markdown converter supports @email@domain.com syntax for mentioning users. These are converted to placeholder mentions that must be resolved before publishing.
How It Works
- •
Markdown like
Hey @john.smith@company.com please reviewconverts to ADF with:json{ "type": "mention", "attrs": { "id": "__EMAIL__:john.smith@company.com", ... } } - •
Before publishing, scan the body content for
__EMAIL__:patterns - •
For each email found, call
mcp__atlassian__lookupJiraAccountIdwith the email - •
If lookup succeeds, replace
__EMAIL__:emailwith the actual account ID - •
If ANY lookup fails, abort immediately with error
Resolution Steps
IMPORTANT: Resolution happens AFTER markdown-to-ADF conversion, on the ADF JSON.
1. Scan the Read tool output for @email@domain.com patterns (do NOT use shell to extract body)
2. For each email found:
- Call: mcp__atlassian__lookupJiraAccountId(query: "email@domain.com")
- If result has accountId, store mapping: email -> accountId
- If no result, add to failed list
3. If failed list not empty:
❌ Cannot publish: Could not resolve mentions for:
- john.smith@company.com
- jane.doe@company.com
Check spelling or use valid Atlassian email addresses.
4. Convert whole draft file to ADF: cat "$draft_file" | bun ~/.dataops-assistant/run skills/jira-publish/scripts/md-to-adf.js
(md-to-adf.js strips frontmatter automatically)
5. The ADF will contain: "id": "__EMAIL__:john.smith@company.com"
6. String-replace in ADF JSON: __EMAIL__:email -> resolved_account_id
7. Pass the modified ADF to Jira API (no --md flag)
Practical approach: Since jira-api.sh handles the markdown conversion internally, you have two options:
Option A (recommended): Convert markdown separately, replace in ADF, pass raw ADF:
# Convert whole draft file to ADF (md-to-adf.js strips frontmatter automatically) adf=$(cat "$draft_file" | bun ~/.dataops-assistant/run skills/jira-publish/scripts/md-to-adf.js) # Replace email placeholders with account IDs adf=$(echo "$adf" | sed "s/__EMAIL__:john@example.com/5b10ac8d.../g") # Pass raw ADF (no --md flag) echo "$adf" | bash "$JIRA_API" create PE Story "Title" -
Option B: Replace @email in markdown with a temporary marker, then fix after:
- •Not recommended - the @email syntax is designed for this flow
Example Preview with Mentions
┌─────────────────────────────────────────────────────────┐ │ PREVIEW: Create Issue │ ├─────────────────────────────────────────────────────────┤ │ Project: PE │ │ Type: Story │ │ Summary: Review needed for API changes │ │ │ │ Mentions to resolve: │ │ ✓ @john.smith@company.com -> John Smith (5b10ac8d...) │ │ ✓ @jane.doe@company.com -> Jane Doe (6c21bd9e...) │ │ │ │ Description (first 500 chars): │ │ ───────────────────────────── │ │ Hey @john.smith@company.com can you review this? │ └─────────────────────────────────────────────────────────┘
Failed Resolution Example
❌ Cannot publish: Could not resolve mentions: - bob.nobody@company.com (no matching Atlassian user) Fix the email addresses in the draft and try again.
Post-Publish Updates
After successful publish, update the draft file:
- •Change
status: drafttostatus: published - •Add
published_issue: PE-1234(the created/updated issue key) - •Write updated file back
Example update:
# Before status: draft published_issue: null # After status: published published_issue: PE-1234
Execution Flow
CRITICAL: Pipe the whole draft file. Do NOT extract the body with awk/sed/shell.
The md-to-adf.js converter strips YAML frontmatter automatically, so piping
the entire file (frontmatter included) is correct and reliable.
For Create
# 1. Build command with optional flags
cmd="bash \"$JIRA_API\" create \"$project\" \"$issue_type\" \"$summary\" - --md"
# Add assignee if present (must be resolved account ID)
[[ -n "$assignee_id" ]] && cmd="$cmd --assignee \"$assignee_id\""
# Add parent if present
[[ -n "$parent" ]] && cmd="$cmd --parent \"$parent\""
# Add components if present (one --component per component)
for comp in "${components[@]}"; do
cmd="$cmd --component \"$comp\""
done
# 2. Pipe the WHOLE draft file (md-to-adf.js strips frontmatter)
# If mentions need resolution, use Option A from Mentions section instead
result=$(cat "$draft_file" | eval "$cmd")
issue_key=$(echo "$result" | jq -r '.issue_key')
# 3. Add links if any (use EXACT type from frontmatter)
for link in links:
bash "$JIRA_API" link "$issue_key" "${link.type}" "${link.target}"
# Example: if frontmatter has {type: blocks, target: PE-456}
# then run: bash "$JIRA_API" link PE-1234 blocks PE-456
# 4. Return URL
echo "$result" | jq -r '.issue_url'
For Update
# Build command with optional flags cmd="bash \"$JIRA_API\" update \"$target_issue\" - --md" # Add fix version if present [[ -n "$fix_version" ]] && cmd="$cmd --fix-version \"$fix_version\"" # Pipe the WHOLE draft file (md-to-adf.js strips frontmatter) result=$(cat "$draft_file" | eval "$cmd") echo "$result" | jq -r '.issue_url'
For Comment
# Build command with optional flags cmd="bash \"$JIRA_API\" comment \"$target_issue\" - --md" # Add fix version if present (will add comment AND set fix version) [[ -n "$fix_version" ]] && cmd="$cmd --fix-version \"$fix_version\"" # Pipe the WHOLE draft file (md-to-adf.js strips frontmatter) result=$(cat "$draft_file" | eval "$cmd") echo "$result" | jq -r '.issue_url'
Output
Success
✅ Published successfully! Issue: PE-1234 URL: https://mcghealth.atlassian.net/browse/PE-1234 Links added: - relates-to MAT-28 Draft updated: .jira-drafts/20250203-143022-openai-instance.md
Failure
❌ Publish failed: Error: [error message from jira-api.sh] Draft NOT modified. Fix the issue and try again.
Example Session
User: /dataops-assistant:jira-publish .jira-drafts/20250203-143022-openai-instance.md
Response:
┌─────────────────────────────────────────────────────────┐ │ PREVIEW: Create Issue │ ├─────────────────────────────────────────────────────────┤ │ Project: PE │ │ Type: Story │ │ Summary: Set up dedicated OpenAI instance for CMS │ │ │ │ Description (first 500 chars): │ │ ───────────────────────────── │ │ :::context │ │ Forever ago, we set up the initial OpenAI instance for │ │ DataSci (MAT-28). Today they still use that single │ │ instance for everything... │ │ ::: │ │ │ │ ## What We Have Today │ │ - Single OpenAI instance shared across all DataSci... │ │ │ │ Links to add after creation: │ │ - relates-to MAT-28 │ └─────────────────────────────────────────────────────────┘ Create this issue in Jira?
User: Yes
Response:
✅ Published successfully! Issue: PE-3456 URL: https://mcghealth.atlassian.net/browse/PE-3456 Links added: - relates-to MAT-28 Draft updated: .jira-drafts/20250203-143022-openai-instance.md
Rules
- •ALWAYS preview before execution - No exceptions
- •ALWAYS ask for approval - User must explicitly confirm
- •Validate before preview - Don't show preview if fields missing
- •Update draft after publish - Mark as published with issue key
- •Return URL - User needs to verify the result
- •Don't modify draft on failure - Only update on success