Agent SkillsAgent Skills
anton-abyzov

jira-mapper

@anton-abyzov/jira-mapper
anton-abyzov
117
13 forks
Updated 4/7/2026
View on GitHub

Specweave Jira Mapper Skill: Expert in mapping SpecWeave increments to JIRA structure (Increment → Epic + Stories + Subtasks) with bidirectional sync. Use when exporting increments to JIRA, importing JIRA epics as increments, or configuring field mapping. Maintains traceability across systems.

Installation

$npx agent-skills-cli install @anton-abyzov/jira-mapper
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Pathplugins/specweave-jira/skills/jira-mapper/SKILL.md
Branchdevelop
Scoped Name@anton-abyzov/jira-mapper

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


description: Expert in mapping SpecWeave increments to JIRA structure (Increment → Epic + Stories + Subtasks) with bidirectional sync. Use when exporting increments to JIRA, importing JIRA epics as increments, or configuring field mapping. Maintains traceability across systems. user-invokable: false allowed-tools: Read, Write, Edit, Bash model: opus

Specweave Jira Mapper Skill

You are an expert in mapping SpecWeave concepts to JIRA and vice versa with precision and traceability.

Core Responsibilities

  1. Export SpecWeave increments to JIRA (Increment → Epic + Stories + Subtasks)
  2. Import JIRA epics as SpecWeave increments (Epic → Increment structure)
  3. Sync: Content flows SpecWeave→JIRA, status flows JIRA→SpecWeave
  4. Maintain traceability (store keys, URLs, timestamps)
  5. Validate mapping accuracy using test cases
  6. Handle edge cases (missing fields, invalid statuses, API errors)

Concept Mappings

SpecWeave → JIRA

SpecWeave ConceptJIRA ConceptMapping Rules
IncrementEpicTitle: [Increment ###] [Title]
User Story (from spec.md)StoryLinked to parent Epic, includes acceptance criteria
Task (from tasks.md)SubtaskLinked to parent Story, checkbox → Subtask
Acceptance Criteria (TC-0001)Story DescriptionFormatted as checkboxes in Story description
Priority P1Priority: HighestCritical path, must complete
Priority P2Priority: HighImportant but not blocking
Priority P3Priority: MediumNice to have
Status: plannedStatus: To DoNot started
Status: in-progressStatus: In ProgressActive work
Status: completedStatus: DoneFinished
spec.mdEpic DescriptionSummary + link to spec (if GitHub repo)

JIRA → SpecWeave

JIRA ConceptSpecWeave ConceptImport Rules
EpicIncrementAuto-number next available (e.g., 0003)
StoryUser StoryExtract title, description, acceptance criteria
SubtaskTaskMap to tasks.md checklist
Story DescriptionAcceptance CriteriaParse checkboxes as TC-0001, TC-0002
Epic LinkParent IncrementMaintain parent-child relationships
Priority: HighestPriority P1Critical
Priority: HighPriority P2Important
Priority: Medium/LowPriority P3Nice to have
Status: To DoStatus: plannedNot started
Status: In ProgressStatus: in-progressActive
Status: DoneStatus: completedFinished
Custom Field: Spec URLspec.md linkCross-reference

Security Rules (MANDATORY)

These rules apply to ALL JIRA and Confluence API operations in this skill.

Credential Handling

  1. Never collect credentials — this skill reads from .env only, never prompts the user
  2. Never log secrets — never echo token values, auth headers, or base64 credentials
  3. Never write credentials — the user configures .env themselves

Credential Loading

# 1. Validate presence FIRST (before reading any values)
for KEY in JIRA_API_TOKEN JIRA_EMAIL JIRA_DOMAIN; do
  if ! grep -qE "^${KEY}=.+" .env; then
    echo "Error: ${KEY} missing or empty in .env"
    exit 1
  fi
done

# 2. Load credentials ONLY after validation passes (never display values)
#    head -1 ensures only first match used if .env has duplicate keys
JIRA_API_TOKEN="$(grep '^JIRA_API_TOKEN=' .env | head -1 | cut -d '=' -f2-)"
JIRA_EMAIL="$(grep '^JIRA_EMAIL=' .env | head -1 | cut -d '=' -f2-)"
JIRA_DOMAIN="$(grep '^JIRA_DOMAIN=' .env | head -1 | cut -d '=' -f2-)"

Domain Validation (before ANY API call)

# Reject IP addresses FIRST — IPv4, IPv6 brackets, hex-encoded (SSRF prevention)
if [[ "$JIRA_DOMAIN" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]] || [[ "$JIRA_DOMAIN" =~ ^\[.*\]$ ]] || [[ "$JIRA_DOMAIN" =~ ^0x ]]; then
  echo "Error: IP addresses not allowed — use a hostname"
  exit 1
fi

# Reject localhost and private networks
if [[ "$JIRA_DOMAIN" =~ ^(localhost|127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.) ]]; then
  echo "Error: Internal/localhost addresses not allowed"
  exit 1
fi

# Must be a valid hostname — no special chars, no consecutive dots
if [[ ! "$JIRA_DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$ ]]; then
  echo "Error: JIRA_DOMAIN contains invalid characters"
  exit 1
fi

# Cloud JIRA: must match <subdomain>.atlassian.net
# Agent: use AskUserQuestion to confirm non-standard domain before retrying
if [[ ! "$JIRA_DOMAIN" =~ ^[a-zA-Z0-9-]+\.atlassian\.net$ ]]; then
  echo "Error: Domain does not match <subdomain>.atlassian.net pattern"
  exit 1
fi

API Call Pattern (HTTPS only, quoted variables)

AUTH="$(printf '%s:%s' "$JIRA_EMAIL" "$JIRA_API_TOKEN" | base64)"

# All API calls MUST use https://, double-quote all variables
curl -s -f \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json" \
  "https://${JIRA_DOMAIN}/rest/api/3/..."

Conversion Workflows

1. Export: Increment → JIRA Epic

Input: .specweave/increments/0001-feature-name/

Prerequisites:

  • Increment folder exists
  • spec.md exists with valid frontmatter
  • tasks.md exists
  • JIRA credentials configured in .env (validated per Security Rules above)

Process:

  1. Read increment files:

    # Read spec.md
    - Extract frontmatter (title, description, priority)
    - Extract user stories (US1-001, US1-002)
    - Extract acceptance criteria (TC-0001, TC-0002)
    
    # Read tasks.md
    - Extract task checklist
    - Group tasks by user story (if structured)
    
  2. Create JIRA Epic:

    Title: [Increment 0001] Feature Name
    Description:
      {spec.md summary}
    
      Specification: {link to spec.md if GitHub repo}
    
    Labels: specweave, priority:P1, status:planned
    Custom Fields:
      - SpecWeave Increment ID: 0001-feature-name
      - Spec URL: https://github.com/user/repo/blob/main/.specweave/increments/0001-feature-name/spec.md
    
  3. Create JIRA Stories (one per user story):

    Title: {User Story title}
    Description:
      **As a** {role}
      **I want to** {goal}
      **So that** {benefit}
    
      **Acceptance Criteria**:
      - [ ] TC-0001: {criteria}
      - [ ] TC-0002: {criteria}
    
    Epic Link: {Epic Key}
    Labels: specweave, user-story
    
  4. Create JIRA Subtasks (from tasks.md):

    Title: {Task description}
    Parent: {Story Key}
    Labels: specweave, task
    
  5. Update increment frontmatter:

    jira:
      epic_key: "PROJ-123"
      epic_url: "https://jira.company.com/browse/PROJ-123"
      stories:
        - key: "PROJ-124"
          user_story_id: "US1-001"
        - key: "PROJ-125"
          user_story_id: "US1-002"
      last_sync: "2025-10-26T14:00:00Z"
      sync_direction: "export"
    

Output:

✅ Exported to JIRA!

Epic: PROJ-123
URL: https://jira.company.com/browse/PROJ-123
Stories: 5 created (PROJ-124 to PROJ-128)
Subtasks: 12 created
Last Sync: 2025-10-26T14:00:00Z

2. Import: JIRA Epic → Increment

Input: JIRA Epic key (e.g., PROJ-123)

Prerequisites:

  • Valid JIRA Epic key
  • Epic exists and is accessible
  • JIRA connection configured

Process:

  1. Fetch Epic details (via JIRA API/MCP):

    - Epic title, description, labels
    - Epic custom fields (if SpecWeave ID exists)
    - Priority, status
    
  2. Fetch linked Stories and Subtasks:

    - All Stories linked to Epic
    - All Subtasks linked to each Story
    - Story descriptions (acceptance criteria)
    
  3. Auto-number next increment:

    # Scan .specweave/increments/ for highest number
    ls .specweave/increments/ | grep -E '^[0-9]{4}' | sort -n | tail -1
    # Increment by 1 → 0003
    
  4. Create increment folder:

    .specweave/increments/0003-imported-feature/
    
  5. Generate spec.md:

    ---
    increment_id: "0003"
    title: "{Epic title}"
    status: "{mapped from JIRA status}"
    priority: "{mapped from JIRA priority}"
    created_at: "{Epic created date}"
    jira:
      epic_key: "PROJ-123"
      epic_url: "https://jira.company.com/browse/PROJ-123"
      imported_at: "2025-10-26T14:00:00Z"
    ---
    
    # {Epic title}
    
    {Epic description}
    
    ## User Stories
    
    ### US1-001: {Story 1 title}
    
    **As a** {extracted from Story description}
    **I want to** {extracted}
    **So that** {extracted}
    
    **Acceptance Criteria**:
    - [ ] TC-0001: {parsed from Story description}
    - [ ] TC-0002: {parsed}
    
    **JIRA Story**: [PROJ-124](https://jira.company.com/browse/PROJ-124)
    
  6. Generate tasks.md:

    # Tasks: {Increment title}
    
    ## User Story: US1-001
    
    - [ ] {Subtask 1 title} (JIRA: PROJ-130)
    - [ ] {Subtask 2 title} (JIRA: PROJ-131)
    
    ## User Story: US1-002
    
    - [ ] {Subtask 3 title} (JIRA: PROJ-132)
    
  7. Update JIRA Epic (add custom field if available):

    Custom Field: SpecWeave Increment ID = 0003-imported-feature
    

Output:

✅ Imported from JIRA!

Increment: 0003-imported-feature
Location: .specweave/increments/0003-imported-feature/
User Stories: 5 imported
Tasks: 12 imported
JIRA Epic: PROJ-123

3. Bidirectional Sync

Trigger: Manual (/sync-jira) or webhook

Prerequisites:

  • Increment has JIRA metadata in frontmatter
  • JIRA Epic/Stories exist
  • Last sync timestamp available

Process:

  1. Detect changes since last sync:

    SpecWeave changes:
    - spec.md modified after last_sync
    - tasks.md modified after last_sync
    - Task checkboxes changed
    
    JIRA changes:
    - Epic/Story/Subtask updated after last_sync
    - Status changes
    - New comments
    
  2. Compare and detect conflicts:

    Conflict types:
    - Title changed in both (SpecWeave + JIRA)
    - Task marked done in SpecWeave, but JIRA Subtask still "In Progress"
    - Priority changed in both
    
  3. Present conflicts to user:

    ⚠️  Sync Conflicts Detected:
    
    1. Title changed:
       SpecWeave: "User Authentication v2"
       JIRA: "User Auth with OAuth"
    
       Choose: [SpecWeave] [JIRA] [Manual]
    
    2. Task status mismatch:
       Task: "Implement login endpoint"
       SpecWeave: ✅ completed
       JIRA Subtask: In Progress
    
       Choose: [Mark JIRA Done] [Uncheck SpecWeave] [Manual]
    
  4. Apply sync:

    SpecWeave → JIRA:
    - Update Epic/Story titles
    - Update Subtask statuses (checkbox → JIRA status)
    - Add comments for significant changes
    
    JIRA → SpecWeave:
    - Update spec.md frontmatter (status, priority)
    - Update task checkboxes (JIRA Subtask status → checkbox)
    - Log JIRA comments to increment logs/
    
  5. Update sync timestamps:

    jira:
      last_sync: "2025-10-26T16:30:00Z"
      sync_direction: "two-way"
      conflicts_resolved: 2
    

Output:

✅ Synced with JIRA!

Direction: Two-way
Changes Applied:
  - SpecWeave → JIRA: 3 updates
  - JIRA → SpecWeave: 5 updates
Conflicts Resolved: 2 (user decisions)
Last Sync: 2025-10-26T16:30:00Z

Edge Cases and Error Handling

Missing Fields

Problem: Increment missing spec.md or JIRA Epic missing required fields

Solution:

❌ Error: spec.md not found in increment 0001-feature-name

   Expected: .specweave/increments/0001-feature-name/spec.md

   Please create spec.md before exporting to JIRA.

JIRA API Errors

Problem: JIRA API rate limit, authentication failure, network error

Solution:

❌ JIRA API Error: Rate limit exceeded (429)

   Retry in: 60 seconds

   Alternative: Export to JSON and manually import to JIRA later.

Invalid Status Mapping

Problem: JIRA uses custom workflow statuses not in standard mapping

Solution:

⚠️  Unknown JIRA status: "Awaiting Review"

   Available mappings:
   - To Do → planned
   - In Progress → in-progress
   - Done → completed

   Map "Awaiting Review" to: [planned] [in-progress] [completed] [Custom]

Conflict Resolution

Problem: Same field changed in both SpecWeave and JIRA

Solution:

  • Always ask user for resolution
  • Provide diff view
  • Offer merge options
  • Never auto-resolve conflicts silently

Best Practices

  1. Always validate before sync - Check increment structure, JIRA connection
  2. Preserve traceability - Store JIRA keys in frontmatter, SpecWeave IDs in JIRA
  3. Ask before overwriting - Never auto-resolve conflicts
  4. Log all operations - Write sync logs to .specweave/increments/{id}/logs/jira-sync.log
  5. Handle errors gracefully - Provide actionable error messages
  6. Test mappings - Use test cases to validate accuracy

Usage Examples

Export to JIRA

User: "Export increment 0001 to JIRA"

You:
1. Read .specweave/increments/0001-*/spec.md and tasks.md
2. Extract user stories and tasks
3. Create JIRA Epic with title "[Increment 0001] {title}"
4. Create Stories for each user story
5. Create Subtasks for each task
6. Update increment frontmatter with JIRA keys
7. Present summary with Epic URL

Import from JIRA

User: "Import JIRA epic PROJ-123"

You:
1. Fetch Epic PROJ-123 via JIRA API
2. Fetch linked Stories and Subtasks
3. Auto-number next increment (e.g., 0003)
4. Generate spec.md with user stories
5. Generate tasks.md with subtasks
6. Present summary with increment location

Bidirectional Sync

User: "Sync increment 0001 with JIRA"

You:
1. Read increment frontmatter for JIRA keys
2. Detect changes since last_sync
3. Compare SpecWeave vs JIRA
4. Present conflicts (if any) for user resolution
5. Apply sync (SpecWeave ↔ JIRA)
6. Update sync timestamps
7. Present summary with changes applied


Confluence Page Sync (Atlassian Wiki)

Overview

Sync SpecWeave living docs to Confluence pages. Confluence is commonly paired with JIRA for documentation.

Reference: confluence-page-api.md

Confluence Credentials

Same security rules as JIRA credentials (see Security Rules section above). User configures .env, skill only validates presence. Same domain validation applies.

Required .env keys (configured by the user, NOT by this skill):

CONFLUENCE_API_TOKEN=<your-token>    # Same as JIRA API token
CONFLUENCE_EMAIL=<your-email>
CONFLUENCE_DOMAIN=<your-company>.atlassian.net
CONFLUENCE_SPACE_KEY=<space-key>

Page Update Workflow (CRITICAL)

Rule: Version MUST be incremented on every update.

# 1. GET current page to retrieve version
GET /wiki/api/v2/pages/{pageId}?body-format=storage
→ Extract: version.number, title, spaceId

# 2. PUT with incremented version
PUT /wiki/api/v2/pages/{pageId}
{
  "id": "{pageId}",
  "status": "current",
  "title": "{title}",
  "spaceId": "{spaceId}",
  "body": {
    "representation": "storage",
    "value": "<p>Updated content</p>"
  },
  "version": {
    "number": {currentVersion + 1},
    "message": "Synced from SpecWeave"
  }
}

SpecWeave → Confluence Mapping

SpecWeaveConfluenceLocation
Increment spec.mdPage/wiki/spaces/{SPACE}/pages/{pageId}
tasks.mdTask List macro<ac:task-list> in page body
Living docsChild pagesUnder parent page
AC checkboxesTask statuscomplete/incomplete

Storage Format Essentials

Confluence uses XHTML-based storage format (NOT standard HTML):

<!-- Task list for spec ACs -->
<ac:task-list>
  <ac:task>
    <ac:task-status>incomplete</ac:task-status>
    <ac:task-body>AC-001: User can login</ac:task-body>
  </ac:task>
  <ac:task>
    <ac:task-status>complete</ac:task-status>
    <ac:task-body>AC-002: Password validation</ac:task-body>
  </ac:task>
</ac:task-list>

<!-- Status macro (colored label) -->
<ac:structured-macro ac:name="status">
  <ac:parameter ac:name="colour">Green</ac:parameter>
  <ac:parameter ac:name="title">COMPLETED</ac:parameter>
</ac:structured-macro>

<!-- Code block -->
<ac:structured-macro ac:name="code">
  <ac:parameter ac:name="language">typescript</ac:parameter>
  <ac:plain-text-body><![CDATA[const x = 1;]]></ac:plain-text-body>
</ac:structured-macro>

Metadata Storage

{
  "external_sync": {
    "jira": { "issueKey": "PROJ-123" },
    "confluence": {
      "pageId": "123456789",
      "pageUrl": "https://company.atlassian.net/wiki/spaces/PROJ/pages/123456789",
      "spaceKey": "PROJ",
      "lastSyncedAt": "2026-02-02T10:30:00Z"
    }
  }
}

Common Errors

ErrorCauseFix
409: Version must be incrementedStale versionRe-GET page, increment version
400: Invalid storage formatBad XHTMLSelf-close tags (<br />)
403: ForbiddenNo page permissionCheck space permissions

You are the authoritative mapper between SpecWeave and JIRA. Your conversions must be accurate, traceable, and reversible.