#
tokens: 47545/50000 42/58 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 3. Use http://codebase.md/nguyenvanduocit/jira-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   └── commands
│       ├── speckit.analyze.md
│       ├── speckit.checklist.md
│       ├── speckit.clarify.md
│       ├── speckit.constitution.md
│       ├── speckit.implement.md
│       ├── speckit.plan.md
│       ├── speckit.specify.md
│       └── speckit.tasks.md
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   └── workflows
│       ├── gitleaks.yaml
│       ├── release.yaml
│       └── scan.yaml
├── .gitignore
├── .specify
│   ├── memory
│   │   └── constitution.md
│   ├── scripts
│   │   └── bash
│   │       ├── check-prerequisites.sh
│   │       ├── common.sh
│   │       ├── create-new-feature.sh
│   │       ├── setup-plan.sh
│   │       └── update-agent-context.sh
│   └── templates
│       ├── agent-file-template.md
│       ├── checklist-template.md
│       ├── plan-template.md
│       ├── spec-template.md
│       └── tasks-template.md
├── CHANGELOG.md
├── CLAUDE.md
├── Dockerfile
├── go.mod
├── go.sum
├── LICENSE
├── main.go
├── prompts
│   └── jira_prompts.go
├── README.md
├── services
│   ├── atlassian.go
│   ├── httpclient.go
│   └── jira.go
├── specs
│   └── 001-i-want-to
│       ├── api-research.md
│       ├── checklists
│       │   ├── implementation-readiness.md
│       │   └── requirements.md
│       ├── contracts
│       │   └── mcp-tool-contract.json
│       ├── data-model.md
│       ├── plan.md
│       ├── quickstart.md
│       ├── research.md
│       ├── spec.md
│       ├── tasks.md
│       ├── test-dev-api.sh
│       └── test-get-build.sh
├── tools
│   ├── jira_comment.go
│   ├── jira_development.go
│   ├── jira_history.go
│   ├── jira_issue.go
│   ├── jira_relationship.go
│   ├── jira_search.go
│   ├── jira_sprint.go
│   ├── jira_status.go
│   ├── jira_transition.go
│   ├── jira_version.go
│   └── jira_worklog.go
└── util
    ├── jira_formatter.go
    └── README.md
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Binaries for programs and plugins
 2 | *.exe
 3 | *.exe~
 4 | *.dll
 5 | *.so
 6 | *.dylib
 7 | 
 8 | # Test binary, built with `go test -c`
 9 | *.test
10 | 
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 | 
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | 
17 | # Go workspace file
18 | go.work
19 | 
20 | # Environment variables
21 | .env
22 | 
23 | # IDE specific files
24 | .idea/
25 | .vscode/
26 | *.swp
27 | *.swo
28 | 
29 | # OS specific files
30 | .DS_Store
31 | Thumbs.db
32 | 
33 | # Build directory
34 | /build/
35 | /dist/
36 | jira-mcp
37 | 
```

--------------------------------------------------------------------------------
/util/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Jira Formatter Utilities
 2 | 
 3 | This package provides utility functions for formatting Jira issues from golang structs to human-readable string representations.
 4 | 
 5 | ## Functions
 6 | 
 7 | ### `FormatJiraIssue(issue *models.IssueScheme) string`
 8 | 
 9 | Converts a complete Jira issue struct to a detailed, formatted string representation. This function handles all available fields from the `IssueFieldsSchemeV2` struct and related schemas.
10 | 
11 | **Features:**
12 | - Basic issue information (Key, ID, URL)
13 | - Complete field information (Summary, Description, Type, Status, Priority, etc.)
14 | - People information (Reporter, Assignee, Creator) with email addresses when available
15 | - Date fields (Created, Updated, Last Viewed, etc.)
16 | - Project and parent issue information
17 | - Work-related fields (Work Ratio, Story Points)
18 | - Collections (Labels, Components, Fix Versions, Affected Versions)
19 | - Relationships (Subtasks, Issue Links)
20 | - Activity summaries (Watchers, Votes, Comments count, Worklogs count)
21 | - Available transitions
22 | - Security information
23 | 
24 | **Usage:**
25 | ```go
26 | import "github.com/nguyenvanduocit/jira-mcp/util"
27 | 
28 | // Get a Jira issue from the API
29 | issue, _, _ := client.Issue.Get(ctx, "PROJ-123", nil, []string{"transitions", "changelog"})
30 | 
31 | // Format it for display
32 | formattedOutput := util.FormatJiraIssue(issue)
33 | fmt.Println(formattedOutput)
34 | ```
35 | 
36 | ### `FormatJiraIssueCompact(issue *models.IssueSchemeV2) string`
37 | 
38 | Returns a compact, single-line representation of a Jira issue suitable for lists or search results.
39 | 
40 | **Features:**
41 | - Key, Summary, Status, Assignee, Priority
42 | - Pipe-separated format for easy scanning
43 | - Null-safe handling
44 | 
45 | **Usage:**
46 | ```go
47 | // For search results or lists
48 | for _, issue := range searchResults.Issues {
49 |     compactLine := util.FormatJiraIssueCompact(issue)
50 |     fmt.Println(compactLine)
51 | }
52 | ```
53 | 
54 | **Example Output:**
55 | ```
56 | Key: PROJ-123 | Summary: Fix login bug | Status: In Progress | Assignee: John Doe | Priority: High
57 | ```
58 | 
59 | ## Refactoring Benefits
60 | 
61 | These utility functions were extracted from duplicate formatting logic in:
62 | - `tools/jira_issue.go` - Get Issue handler
63 | - `tools/jira_search.go` - Search Issues handler
64 | 
65 | **Benefits:**
66 | 1. **DRY Principle**: Eliminates code duplication
67 | 2. **Comprehensive**: Handles all fields from the Jira issue struct
68 | 3. **Consistent**: Ensures uniform formatting across all tools
69 | 4. **Maintainable**: Single location for formatting logic updates
70 | 5. **Flexible**: Provides both detailed and compact formatting options
71 | 6. **Robust**: Includes null-safe handling for all optional fields
72 | 
73 | ## Implementation Notes
74 | 
75 | - All fields are handled with null-safe checks to prevent panics
76 | - Optional fields gracefully show "None", "Unassigned", or are omitted when empty
77 | - Collections (arrays/slices) are formatted as lists when present
78 | - Story point estimates are extracted from changelog history when available
79 | - Email addresses are shown in parentheses when available for user fields 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | ## Jira MCP
  2 | 
  3 | An opinionated Jira MCP server built from years of real-world software development experience.
  4 | 
  5 | Unlike generic Jira integrations, this MCP is crafted from the daily workflows of engineers and automation QC teams. You'll find sophisticated tools designed for actual development needs—like retrieving all pull requests linked to an issue, managing complex sprint transitions, or tracking development information across your entire workflow.
  6 | 
  7 | This isn't just another API wrapper. It's a reflection of how professionals actually use Jira: managing sprints, tracking development work, coordinating releases, and maintaining visibility across teams. Every tool is designed to solve real problems that arise in modern software development.
  8 | 
  9 | ## Available tools
 10 | 
 11 | ### Issue Management
 12 | - **jira_get_issue** - Retrieve detailed information about a specific issue including status, assignee, description, subtasks, and available transitions
 13 | - **jira_create_issue** - Create a new issue with specified details (returns key, ID, and URL)
 14 | - **jira_create_child_issue** - Create a child issue (sub-task) linked to a parent issue
 15 | - **jira_update_issue** - Modify an existing issue's details (supports partial updates)
 16 | - **jira_list_issue_types** - List all available issue types in a project with their IDs, names, and descriptions
 17 | 
 18 | ### Search
 19 | - **jira_search_issue** - Search for issues using JQL (Jira Query Language) with customizable fields and expand options
 20 | 
 21 | ### Sprint Management
 22 | - **jira_list_sprints** - List all active and future sprints for a specific board or project
 23 | - **jira_get_sprint** - Retrieve detailed information about a specific sprint by its ID
 24 | - **jira_get_active_sprint** - Get the currently active sprint for a given board or project
 25 | - **jira_search_sprint_by_name** - Search for sprints by name with exact or partial matching
 26 | 
 27 | ### Status & Transitions
 28 | - **jira_list_statuses** - Retrieve all available issue status IDs and their names for a project
 29 | - **jira_transition_issue** - Transition an issue through its workflow using a valid transition ID
 30 | 
 31 | ### Comments
 32 | - **jira_add_comment** - Add a comment to an issue (uses Atlassian Document Format)
 33 | - **jira_get_comments** - Retrieve all comments from an issue
 34 | 
 35 | ### Worklogs
 36 | - **jira_add_worklog** - Add a worklog entry to track time spent on an issue
 37 | 
 38 | ### History & Audit
 39 | - **jira_get_issue_history** - Retrieve the complete change history of an issue
 40 | 
 41 | ### Issue Relationships
 42 | - **jira_get_related_issues** - Retrieve issues that have a relationship (blocks, is blocked by, relates to, etc.)
 43 | - **jira_link_issues** - Create a link between two issues, defining their relationship
 44 | 
 45 | ### Version Management
 46 | - **jira_get_version** - Retrieve detailed information about a specific project version
 47 | - **jira_list_project_versions** - List all versions in a project with their details
 48 | 
 49 | ### Development Information
 50 | - **jira_get_development_information** - Retrieve branches, pull requests, and commits linked to an issue via development tool integrations (GitHub, GitLab, Bitbucket)
 51 | 
 52 | 
 53 | 
 54 | ## Installation
 55 | 
 56 | Copy this prompt to your AI assistant:
 57 | 
 58 | ```
 59 | Install the Jira MCP server (https://github.com/nguyenvanduocit/jira-mcp) for my Claude Desktop or Cursor IDE. Read the MCP documentation carefully and guide me through the installation step by step.
 60 | ```
 61 | 
 62 | If your AI assistant cannot help with this installation, it indicates either a misconfiguration or an ineffective AI tool. A capable AI assistant should be able to guide you through MCP installation.
 63 | 
 64 | ## License
 65 | MIT — see `LICENSE`.
 66 | 
 67 | ## FOR AI
 68 | 
 69 | > THIS SECTION IS FOR AI ONLY
 70 | 
 71 | When working with this codebase, read these files to understand the project structure:
 72 | 
 73 | 1. **CLAUDE.md** - Comprehensive project documentation including architecture, development commands, and coding conventions
 74 | 2. **main.go** - Entry point that shows how the MCP server is initialized and tools are registered
 75 | 3. **services/jira_client.go** - Singleton Jira client initialization and authentication
 76 | 4. **tools/** - Individual tool implementations following consistent patterns
 77 | 5. **docs/** - Detailed documentation (see structure below)
 78 | 
 79 | Key concepts:
 80 | - This is a Go-based MCP server that connects AI assistants to Jira
 81 | - Each tool follows a registration + handler pattern with typed input validation
 82 | - Tools are organized by category (issues, sprints, comments, worklogs, etc.)
 83 | - All Jira operations use the `github.com/ctreminiom/go-atlassian` client library
 84 | - Development principles documented in `.specify/memory/constitution.md`
 85 | 
 86 | Before making changes, review:
 87 | - **CLAUDE.md** for architecture patterns and development commands
 88 | - **.specify/memory/constitution.md** for governance principles
 89 | 
 90 | 
 91 | ## Quick start
 92 | 
 93 | ### 1) Get an API token
 94 | Create one at `https://id.atlassian.com/manage-profile/security/api-tokens`.
 95 | 
 96 | ### 2) Add to Cursor
 97 | Use Docker or a local binary (STDIO; no ports needed).
 98 | 
 99 | #### Docker
100 | ```json
101 | {
102 |   "mcpServers": {
103 |     "jira": {
104 |       "command": "docker",
105 |       "args": [
106 |         "run", "--rm", "-i",
107 |         "-e", "ATLASSIAN_HOST=https://your-company.atlassian.net",
108 |         "-e", "[email protected]",
109 |         "-e", "ATLASSIAN_TOKEN=your-api-token",
110 |         "ghcr.io/nguyenvanduocit/jira-mcp:latest"
111 |       ]
112 |     }
113 |   }
114 | }
115 | ```
116 | 
117 | #### Binary
118 | ```json
119 | {
120 |   "mcpServers": {
121 |     "jira": {
122 |       "command": "/usr/local/bin/jira-mcp",
123 |       "env": {
124 |         "ATLASSIAN_HOST": "https://your-company.atlassian.net",
125 |         "ATLASSIAN_EMAIL": "[email protected]",
126 |         "ATLASSIAN_TOKEN": "your-api-token"
127 |       }
128 |     }
129 |   }
130 | }
131 | ```
132 | 
133 | ### 3) Try it in Cursor
134 | - “Show my issues assigned to me”
135 | - “What’s in the current sprint for ABC?”
136 | - “Create a bug in ABC: Login fails on Safari”
137 | 
138 | ## Configuration
139 | - **ATLASSIAN_HOST**: `https://your-company.atlassian.net`
140 | - **ATLASSIAN_EMAIL**: your Atlassian email
141 | - **ATLASSIAN_TOKEN**: API token
142 | 
143 | Optional `.env` (if running locally):
144 | ```bash
145 | ATLASSIAN_HOST=https://your-company.atlassian.net
146 | [email protected]
147 | ATLASSIAN_TOKEN=your-api-token
148 | ```
149 | 
150 | HTTP mode (optional, for debugging):
151 | ```bash
152 | jira-mcp -env .env -http_port 3000
153 | ```
154 | Cursor config (HTTP mode):
155 | ```json
156 | { "mcpServers": { "jira": { "url": "http://localhost:3000/mcp" } } }
157 | ```
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
  1 | # CLAUDE.md
  2 | 
  3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
  4 | 
  5 | ## Project Overview
  6 | 
  7 | This is a Jira MCP (Model Control Protocol) connector written in Go that enables AI assistants like Claude to interact with Atlassian Jira. The project provides a comprehensive set of tools for managing Jira issues, sprints, comments, worklogs, and more through structured MCP tool calls.
  8 | 
  9 | ## Development Commands
 10 | 
 11 | ```bash
 12 | # Build the project
 13 | go build -o jira-mcp .
 14 | 
 15 | # Run in development mode with HTTP server (requires .env file)
 16 | go run . --env .env --http_port 3002
 17 | 
 18 | # Run tests
 19 | go test ./...
 20 | 
 21 | # Install locally
 22 | go install
 23 | 
 24 | # Use go doc to understand packages and types
 25 | go doc <pkg>
 26 | go doc <sym>[.<methodOrField>]
 27 | ```
 28 | 
 29 | ## Architecture Overview
 30 | 
 31 | ### Core Structure
 32 | - **main.go** - Entry point that initializes the MCP server, validates environment variables, and registers all tools
 33 | - **services/** - Service layer containing Jira client setup and authentication
 34 | - **tools/** - Tool implementations organized by functionality (issues, sprints, comments, etc.)
 35 | - **util/** - Utility functions for error handling and response formatting
 36 | 
 37 | ### Key Dependencies
 38 | - `github.com/ctreminiom/go-atlassian` - Go client library for Atlassian APIs
 39 | - `github.com/mark3labs/mcp-go` - Go implementation of Model Control Protocol
 40 | - `github.com/joho/godotenv` - Environment variable loading
 41 | 
 42 | ### Tool Implementation Pattern
 43 | 
 44 | Each Jira operation follows this consistent pattern using **typed handlers**:
 45 | 
 46 | 1. **Input Struct** - Define typed input with validation tags
 47 | 2. **Registration Function** (`RegisterJira<Category>Tool`) - Creates tool definitions and registers them with the MCP server
 48 | 3. **Typed Handler Function** - Processes tool calls with compile-time type safety
 49 | 
 50 | Example tool structure:
 51 | ```go
 52 | // 1. Define input struct with validation
 53 | type GetIssueInput struct {
 54 |     IssueKey string `json:"issue_key" validate:"required"`
 55 |     Fields   string `json:"fields,omitempty"`
 56 |     Expand   string `json:"expand,omitempty"`
 57 | }
 58 | 
 59 | // 2. Registration function
 60 | func RegisterJiraIssueTool(s *server.MCPServer) {
 61 |     tool := mcp.NewTool("jira_get_issue",
 62 |         mcp.WithDescription("..."),
 63 |         mcp.WithString("issue_key", mcp.Required(), mcp.Description("...")),
 64 |         mcp.WithString("fields", mcp.Description("...")),
 65 |     )
 66 |     s.AddTool(tool, mcp.NewTypedToolHandler(jiraGetIssueHandler))
 67 | }
 68 | 
 69 | // 3. Typed handler with automatic validation
 70 | func jiraGetIssueHandler(ctx context.Context, request mcp.CallToolRequest, input GetIssueInput) (*mcp.CallToolResult, error) {
 71 |     client := services.JiraClient()
 72 |     // Direct access to validated parameters - no type assertions needed
 73 |     issue, response, err := client.Issue.Get(ctx, input.IssueKey, fields, expand)
 74 |     if err != nil {
 75 |         return nil, fmt.Errorf("failed to get issue: %v", err)
 76 |     }
 77 |     return mcp.NewToolResultText(util.FormatIssue(issue)), nil
 78 | }
 79 | ```
 80 | 
 81 | ### Available Tool Categories
 82 | - **Issue Management** - Create, read, update issues and subtasks
 83 | - **Search** - JQL-based issue searching
 84 | - **Sprint Management** - List sprints, move issues between sprints
 85 | - **Status & Transitions** - Get available statuses and transition issues
 86 | - **Comments** - Add and retrieve issue comments (uses Atlassian Document Format)
 87 | - **Worklogs** - Time tracking functionality
 88 | - **History** - Issue change history and audit logs
 89 | - **Relationships** - Link and relate issues
 90 | - **Versions** - Project version management
 91 | - **Development Information** - Retrieve branches, pull requests, and commits linked to issues
 92 | 
 93 | ## Configuration
 94 | 
 95 | The application requires these environment variables:
 96 | - `ATLASSIAN_HOST` - Your Atlassian instance URL (e.g., https://company.atlassian.net)
 97 | - `ATLASSIAN_EMAIL` - Your Atlassian account email
 98 | - `ATLASSIAN_TOKEN` - API token from Atlassian
 99 | 
100 | Environment variables can be loaded from a `.env` file using the `--env` flag.
101 | 
102 | ## Service Architecture
103 | 
104 | ### Jira Client Initialization
105 | The `services.JiraClient()` function uses `sync.OnceValue` to create a singleton Jira client instance with basic authentication. This ensures efficient connection reuse across all tool calls.
106 | 
107 | ### HTTP vs STDIO Modes
108 | The server can run in two modes:
109 | - **STDIO mode** (default) - Standard MCP protocol over stdin/stdout
110 | - **HTTP mode** (`--http_port` flag) - HTTP server for development and testing
111 | 
112 | ## Testing and Deployment
113 | 
114 | The project includes:
115 | - Docker support with multi-stage builds
116 | - GitHub Actions for automated releases
117 | - Binary releases for multiple platforms (macOS, Linux, Windows)
118 | 
119 | ## Code Conventions
120 | 
121 | - Use structured input types for tool parameters with JSON tags and validation
122 | - All tool handlers should return `*mcp.CallToolResult` with formatted text or JSON
123 | - Client initialization should use the singleton pattern from services package
124 | - Response formatting should be human-readable for AI consumption
125 | - Comments MUST use Atlassian Document Format (ADF) with proper structure:
126 |   ```go
127 |   // ADF structure for comments
128 |   Body: &models.CommentNodeScheme{
129 |       Version: 1,
130 |       Type:    "doc",
131 |       Content: []*models.CommentNodeScheme{
132 |           {
133 |               Type: "paragraph",
134 |               Content: []*models.CommentNodeScheme{
135 |                   {Type: "text", Text: "comment text"},
136 |               },
137 |           },
138 |       },
139 |   }
140 |   ```
141 | 
142 | ## Governance
143 | 
144 | This project follows strict governance principles documented in `.specify/memory/constitution.md`. Key principles include:
145 | 
146 | - **MCP Protocol Compliance** - All functionality MUST be exposed as MCP tools
147 | - **AI-First Output Design** - Responses formatted for LLM consumption
148 | - **Simplicity Over Abstraction** - Avoid unnecessary helper functions and layers
149 | - **Type Safety & Validation** - Use typed handlers with input structs
150 | - **Resource Efficiency** - Singleton pattern for client connections
151 | - **Error Transparency** - Include endpoint context in error messages
152 | 
153 | Before implementing new features or making changes, consult the constitution for detailed requirements and patterns.
154 | 
```

--------------------------------------------------------------------------------
/.github/workflows/scan.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Security and Licence Scan
 2 | 
 3 | on:
 4 |   pull_request:
 5 | 
 6 | jobs:
 7 |   test:
 8 |     runs-on: ubuntu-latest
 9 |     steps:
10 |     - name: Checkout code
11 |       uses: actions/checkout@v4
12 |       with:
13 |         fetch-depth: 0
14 |     - name: Secret Scanning
15 |       uses: trufflesecurity/trufflehog@main
16 |       with:
17 |         extra_args: --results=verified,unknown
```

--------------------------------------------------------------------------------
/.github/workflows/gitleaks.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: gitleaks
 2 | on:
 3 |   pull_request:
 4 |   push:
 5 |   workflow_dispatch:
 6 |   schedule:
 7 |     - cron: "0 4 * * *"
 8 | jobs:
 9 |   scan:
10 |     name: gitleaks
11 |     runs-on: ubuntu-latest
12 |     steps:
13 |       - uses: actions/checkout@v4
14 |         with:
15 |           fetch-depth: 0
16 |       - uses: gitleaks/gitleaks-action@v2
17 |         env:
18 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM golang:1.23-alpine AS builder
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | COPY go.mod go.sum ./
 6 | 
 7 | RUN go mod download
 8 | 
 9 | COPY . .
10 | 
11 | # Use build arguments for cross-compilation
12 | ARG TARGETOS
13 | ARG TARGETARCH
14 | 
15 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o jira-mcp .
16 | 
17 | FROM alpine:latest
18 | 
19 | WORKDIR /app
20 | 
21 | COPY --from=builder /app/jira-mcp .
22 | 
23 | # Expose port for SSE server (optional)
24 | EXPOSE 8080
25 | 
26 | ENTRYPOINT ["/app/jira-mcp"]
27 | 
28 | CMD []
```

--------------------------------------------------------------------------------
/.specify/templates/agent-file-template.md:
--------------------------------------------------------------------------------

```markdown
 1 | # [PROJECT NAME] Development Guidelines
 2 | 
 3 | Auto-generated from all feature plans. Last updated: [DATE]
 4 | 
 5 | ## Active Technologies
 6 | [EXTRACTED FROM ALL PLAN.MD FILES]
 7 | 
 8 | ## Project Structure
 9 | ```
10 | [ACTUAL STRUCTURE FROM PLANS]
11 | ```
12 | 
13 | ## Commands
14 | [ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]
15 | 
16 | ## Code Style
17 | [LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]
18 | 
19 | ## Recent Changes
20 | [LAST 3 FEATURES AND WHAT THEY ADDED]
21 | 
22 | <!-- MANUAL ADDITIONS START -->
23 | <!-- MANUAL ADDITIONS END -->
```

--------------------------------------------------------------------------------
/services/httpclient.go:
--------------------------------------------------------------------------------

```go
 1 | package services
 2 | 
 3 | import (
 4 | 	"crypto/tls"
 5 | 	"fmt"
 6 | 	"net/http"
 7 | 	"net/url"
 8 | 	"os"
 9 | 	"sync"
10 | )
11 | 
12 | var DefaultHttpClient = sync.OnceValue(func() *http.Client {
13 | 	transport := &http.Transport{}
14 | 
15 | 	proxyURL := os.Getenv("PROXY_URL")
16 | 	if proxyURL != "" {
17 | 		proxy, err := url.Parse(proxyURL)
18 | 		if err != nil {
19 | 			panic(fmt.Sprintf("Failed to parse PROXY_URL: %v", err))
20 | 		}
21 | 		transport.Proxy = http.ProxyURL(proxy)
22 | 		transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
23 | 	}
24 | 
25 | 	return &http.Client{Transport: transport}
26 | })
27 | 
```

--------------------------------------------------------------------------------
/services/jira.go:
--------------------------------------------------------------------------------

```go
 1 | package services
 2 | 
 3 | import (
 4 | 	"log"
 5 | 	"sync"
 6 | 
 7 | 	jira "github.com/ctreminiom/go-atlassian/jira/v3"
 8 | 	"github.com/pkg/errors"
 9 | )
10 | 
11 | var JiraClient = sync.OnceValue[*jira.Client](func() *jira.Client {
12 | 	host, mail, token := loadAtlassianCredentials()
13 | 
14 | 	if host == "" || mail == "" || token == "" {
15 | 		log.Fatal("ATLASSIAN_HOST, ATLASSIAN_EMAIL, ATLASSIAN_TOKEN are required")
16 | 	}
17 | 
18 | 	instance, err := jira.New(nil, host)
19 | 	if err != nil {
20 | 		log.Fatal(errors.WithMessage(err, "failed to create jira client"))
21 | 	}
22 | 
23 | 	instance.Auth.SetBasicAuth(mail, token)
24 | 
25 | 	return instance
26 | })
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Feature request
 3 | about: Suggest an idea for this project
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | 
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 | 
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 | 
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 | 
```

--------------------------------------------------------------------------------
/services/atlassian.go:
--------------------------------------------------------------------------------

```go
 1 | package services
 2 | 
 3 | import (
 4 | 	"log"
 5 | 	"os"
 6 | 	"sync"
 7 | 
 8 | 	"github.com/ctreminiom/go-atlassian/jira/agile"
 9 | 	"github.com/pkg/errors"
10 | )
11 | 
12 | func loadAtlassianCredentials() (host, mail, token string) {
13 | 	host = os.Getenv("ATLASSIAN_HOST")
14 | 	mail = os.Getenv("ATLASSIAN_EMAIL")
15 | 	token = os.Getenv("ATLASSIAN_TOKEN")
16 | 
17 | 	if host == "" || mail == "" || token == "" {
18 | 		log.Fatal("ATLASSIAN_HOST, ATLASSIAN_EMAIL, ATLASSIAN_TOKEN are required, please set it in MCP Config")
19 | 	}
20 | 
21 | 	return host, mail, token
22 | }
23 | 
24 | var AgileClient = sync.OnceValue[*agile.Client](func() *agile.Client {
25 | 	host, mail, token := loadAtlassianCredentials()
26 | 
27 | 	instance, err := agile.New(nil, host)
28 | 	if err != nil {
29 | 		log.Fatal(errors.WithMessage(err, "failed to create agile client"))
30 | 	}
31 | 
32 | 	instance.Auth.SetBasicAuth(mail, token)
33 | 
34 | 	return instance
35 | })
36 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 | 
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 | 
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 | 
26 | **Desktop (please complete the following information):**
27 |  - OS: [e.g. iOS]
28 |  - Browser [e.g. chrome, safari]
29 |  - Version [e.g. 22]
30 | 
31 | **Smartphone (please complete the following information):**
32 |  - Device: [e.g. iPhone6]
33 |  - OS: [e.g. iOS8.1]
34 |  - Browser [e.g. stock browser, safari]
35 |  - Version [e.g. 22]
36 | 
37 | **Additional context**
38 | Add any other context about the problem here.
39 | 
```

--------------------------------------------------------------------------------
/.specify/templates/checklist-template.md:
--------------------------------------------------------------------------------

```markdown
 1 | # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
 2 | 
 3 | **Purpose**: [Brief description of what this checklist covers]
 4 | **Created**: [DATE]
 5 | **Feature**: [Link to spec.md or relevant documentation]
 6 | 
 7 | **Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.
 8 | 
 9 | <!-- 
10 |   ============================================================================
11 |   IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
12 |   
13 |   The /speckit.checklist command MUST replace these with actual items based on:
14 |   - User's specific checklist request
15 |   - Feature requirements from spec.md
16 |   - Technical context from plan.md
17 |   - Implementation details from tasks.md
18 |   
19 |   DO NOT keep these sample items in the generated checklist file.
20 |   ============================================================================
21 | -->
22 | 
23 | ## [Category 1]
24 | 
25 | - [ ] CHK001 First checklist item with clear action
26 | - [ ] CHK002 Second checklist item
27 | - [ ] CHK003 Third checklist item
28 | 
29 | ## [Category 2]
30 | 
31 | - [ ] CHK004 Another category item
32 | - [ ] CHK005 Item with specific criteria
33 | - [ ] CHK006 Final item in this category
34 | 
35 | ## Notes
36 | 
37 | - Check items off as completed: `[x]`
38 | - Add comments or findings inline
39 | - Link to relevant resources or documentation
40 | - Items are numbered sequentially for easy reference
41 | 
```

--------------------------------------------------------------------------------
/specs/001-i-want-to/checklists/requirements.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Specification Quality Checklist: Retrieve Development Information from Jira Issue
 2 | 
 3 | **Purpose**: Validate specification completeness and quality before proceeding to planning
 4 | **Created**: 2025-10-07
 5 | **Feature**: [spec.md](../spec.md)
 6 | 
 7 | ## Content Quality
 8 | 
 9 | - [x] No implementation details (languages, frameworks, APIs)
10 | - [x] Focused on user value and business needs
11 | - [x] Written for non-technical stakeholders
12 | - [x] All mandatory sections completed
13 | 
14 | ## Requirement Completeness
15 | 
16 | - [x] No [NEEDS CLARIFICATION] markers remain
17 | - [x] Requirements are testable and unambiguous
18 | - [x] Success criteria are measurable
19 | - [x] Success criteria are technology-agnostic (no implementation details)
20 | - [x] All acceptance scenarios are defined
21 | - [x] Edge cases are identified
22 | - [x] Scope is clearly bounded
23 | - [x] Dependencies and assumptions identified
24 | 
25 | ## Feature Readiness
26 | 
27 | - [x] All functional requirements have clear acceptance criteria
28 | - [x] User scenarios cover primary flows
29 | - [x] Feature meets measurable outcomes defined in Success Criteria
30 | - [x] No implementation details leak into specification
31 | 
32 | ## Notes
33 | 
34 | **Clarification Resolved**: FR-011 now specifies using Jira's standard `/rest/dev-status/1.0/issue/detail` endpoint via the go-atlassian client.
35 | 
36 | **Validation Status**: ✅ All checklist items pass. Specification is ready for `/speckit.plan` phase.
37 | 
```

--------------------------------------------------------------------------------
/specs/001-i-want-to/test-get-build.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # Load environment variables from .env file
 4 | set -a
 5 | source "$(dirname "$0")/../../.env"
 6 | set +a
 7 | 
 8 | JIRA_HOST="${ATLASSIAN_HOST}"
 9 | JIRA_EMAIL="${ATLASSIAN_EMAIL}"
10 | JIRA_TOKEN="${ATLASSIAN_TOKEN}"
11 | ISSUE_KEY="SHTP-6050"
12 | 
13 | echo "========================================="
14 | echo "Step 1: Getting numeric issue ID for ${ISSUE_KEY}"
15 | echo "========================================="
16 | 
17 | ISSUE_RESPONSE=$(curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
18 |   -H "Accept: application/json" \
19 |   "${JIRA_HOST}/rest/api/3/issue/${ISSUE_KEY}?fields=id")
20 | 
21 | echo "$ISSUE_RESPONSE" | jq '.'
22 | 
23 | ISSUE_ID=$(echo "$ISSUE_RESPONSE" | jq -r '.id')
24 | 
25 | if [ -z "$ISSUE_ID" ] || [ "$ISSUE_ID" = "null" ]; then
26 |   echo "ERROR: Could not get issue ID"
27 |   exit 1
28 | fi
29 | 
30 | echo ""
31 | echo "Issue ID: ${ISSUE_ID}"
32 | echo ""
33 | 
34 | echo "========================================="
35 | echo "Step 2: Getting development summary"
36 | echo "========================================="
37 | 
38 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
39 |   -H "Accept: application/json" \
40 |   "${JIRA_HOST}/rest/dev-status/latest/issue/summary?issueId=${ISSUE_ID}" | jq '.'
41 | 
42 | echo ""
43 | echo "========================================="
44 | echo "Step 8: Getting build information - cloud-providers"
45 | echo "========================================="
46 | 
47 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
48 |   -H "Accept: application/json" \
49 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=cloud-providers&dataType=build" | jq '.'
50 | 
```

--------------------------------------------------------------------------------
/.specify/scripts/bash/setup-plan.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -e
 4 | 
 5 | # Parse command line arguments
 6 | JSON_MODE=false
 7 | ARGS=()
 8 | 
 9 | for arg in "$@"; do
10 |     case "$arg" in
11 |         --json) 
12 |             JSON_MODE=true 
13 |             ;;
14 |         --help|-h) 
15 |             echo "Usage: $0 [--json]"
16 |             echo "  --json    Output results in JSON format"
17 |             echo "  --help    Show this help message"
18 |             exit 0 
19 |             ;;
20 |         *) 
21 |             ARGS+=("$arg") 
22 |             ;;
23 |     esac
24 | done
25 | 
26 | # Get script directory and load common functions
27 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28 | source "$SCRIPT_DIR/common.sh"
29 | 
30 | # Get all paths and variables from common functions
31 | eval $(get_feature_paths)
32 | 
33 | # Check if we're on a proper feature branch (only for git repos)
34 | check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
35 | 
36 | # Ensure the feature directory exists
37 | mkdir -p "$FEATURE_DIR"
38 | 
39 | # Copy plan template if it exists
40 | TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md"
41 | if [[ -f "$TEMPLATE" ]]; then
42 |     cp "$TEMPLATE" "$IMPL_PLAN"
43 |     echo "Copied plan template to $IMPL_PLAN"
44 | else
45 |     echo "Warning: Plan template not found at $TEMPLATE"
46 |     # Create a basic plan file if template doesn't exist
47 |     touch "$IMPL_PLAN"
48 | fi
49 | 
50 | # Output results
51 | if $JSON_MODE; then
52 |     printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \
53 |         "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT"
54 | else
55 |     echo "FEATURE_SPEC: $FEATURE_SPEC"
56 |     echo "IMPL_PLAN: $IMPL_PLAN" 
57 |     echo "SPECS_DIR: $FEATURE_DIR"
58 |     echo "BRANCH: $CURRENT_BRANCH"
59 |     echo "HAS_GIT: $HAS_GIT"
60 | fi
61 | 
```

--------------------------------------------------------------------------------
/tools/jira_status.go:
--------------------------------------------------------------------------------

```go
 1 | package tools
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"strings"
 7 | 
 8 | 	"github.com/mark3labs/mcp-go/mcp"
 9 | 	"github.com/mark3labs/mcp-go/server"
10 | 	"github.com/nguyenvanduocit/jira-mcp/services"
11 | )
12 | 
13 | // Input types for typed tools
14 | type ListStatusesInput struct {
15 | 	ProjectKey string `json:"project_key" validate:"required"`
16 | }
17 | 
18 | func RegisterJiraStatusTool(s *server.MCPServer) {
19 | 	jiraStatusListTool := mcp.NewTool("jira_list_statuses",
20 | 		mcp.WithDescription("Retrieve all available issue status IDs and their names for a specific Jira project"),
21 | 		mcp.WithString("project_key", mcp.Required(), mcp.Description("Project identifier (e.g., KP, PROJ)")),
22 | 	)
23 | 	s.AddTool(jiraStatusListTool, mcp.NewTypedToolHandler(jiraGetStatusesHandler))
24 | }
25 | 
26 | func jiraGetStatusesHandler(ctx context.Context, request mcp.CallToolRequest, input ListStatusesInput) (*mcp.CallToolResult, error) {
27 | 	client := services.JiraClient()
28 | 
29 | 	issueTypes, response, err := client.Project.Statuses(ctx, input.ProjectKey)
30 | 	if err != nil {
31 | 		if response != nil {
32 | 			return nil, fmt.Errorf("failed to get statuses: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
33 | 		}
34 | 		return nil, fmt.Errorf("failed to get statuses: %v", err)
35 | 	}
36 | 
37 | 	if len(issueTypes) == 0 {
38 | 		return mcp.NewToolResultText("No issue types found for this project."), nil
39 | 	}
40 | 
41 | 	var result strings.Builder
42 | 	result.WriteString("Available Statuses:\n")
43 | 	for _, issueType := range issueTypes {
44 | 		result.WriteString(fmt.Sprintf("\nIssue Type: %s\n", issueType.Name))
45 | 		for _, status := range issueType.Statuses {
46 | 			result.WriteString(fmt.Sprintf("  - %s: %s\n", status.Name, status.ID))
47 | 		}
48 | 	}
49 | 
50 | 	return mcp.NewToolResultText(result.String()), nil
51 | }
52 | 
```

--------------------------------------------------------------------------------
/tools/jira_transition.go:
--------------------------------------------------------------------------------

```go
 1 | package tools
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
 8 | 	"github.com/mark3labs/mcp-go/mcp"
 9 | 	"github.com/mark3labs/mcp-go/server"
10 | 	"github.com/nguyenvanduocit/jira-mcp/services"
11 | )
12 | 
13 | // Input types for typed tools
14 | type TransitionIssueInput struct {
15 | 	IssueKey     string `json:"issue_key" validate:"required"`
16 | 	TransitionID string `json:"transition_id" validate:"required"`
17 | 	Comment      string `json:"comment,omitempty"`
18 | }
19 | 
20 | func RegisterJiraTransitionTool(s *server.MCPServer) {
21 | 	jiraTransitionTool := mcp.NewTool("jira_transition_issue",
22 | 		mcp.WithDescription("Transition an issue through its workflow using a valid transition ID. Get available transitions from jira_get_issue"),
23 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The issue to transition (e.g., KP-123)")),
24 | 		mcp.WithString("transition_id", mcp.Required(), mcp.Description("Transition ID from available transitions list")),
25 | 		mcp.WithString("comment", mcp.Description("Optional comment to add with transition")),
26 | 	)
27 | 	s.AddTool(jiraTransitionTool, mcp.NewTypedToolHandler(jiraTransitionIssueHandler))
28 | }
29 | 
30 | func jiraTransitionIssueHandler(ctx context.Context, request mcp.CallToolRequest, input TransitionIssueInput) (*mcp.CallToolResult, error) {
31 | 	client := services.JiraClient()
32 | 
33 | 	var options *models.IssueMoveOptionsV3
34 | 	if input.Comment != "" {
35 | 		options = &models.IssueMoveOptionsV3{
36 | 			Fields: &models.IssueScheme{},
37 | 		}
38 | 	}
39 | 
40 | 	response, err := client.Issue.Move(ctx, input.IssueKey, input.TransitionID, options)
41 | 	if err != nil {
42 | 		if response != nil {
43 | 			return nil, fmt.Errorf("transition failed: %s (endpoint: %s)",
44 | 				response.Bytes.String(),
45 | 				response.Endpoint)
46 | 		}
47 | 		return nil, fmt.Errorf("transition failed: %v", err)
48 | 	}
49 | 
50 | 	return mcp.NewToolResultText("Issue transition completed successfully"), nil
51 | }
52 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release Please and GoReleaser
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 | 
 8 | permissions:
 9 |   contents: write
10 |   pull-requests: write
11 |   packages: write
12 | 
13 | jobs:
14 | 
15 |   release-please:
16 |     runs-on: ubuntu-latest
17 |     outputs:
18 |       release_created: ${{ steps.release.outputs.release_created }}
19 |       tag_name: ${{ steps.release.outputs.tag_name }}
20 |     steps:
21 |       - uses: googleapis/release-please-action@v4
22 |         id: release
23 |         with:
24 |           token: ${{ secrets.GITHUB_TOKEN }}
25 |           release-type: go
26 | 
27 |   goreleaser:
28 |     needs: release-please
29 |     if: ${{ needs.release-please.outputs.release_created }}
30 |     runs-on: ubuntu-latest
31 |     steps:
32 |       - name: Checkout
33 |         uses: actions/checkout@v4
34 |         with:
35 |           fetch-depth: 0
36 |       - name: Set up Go
37 |         uses: actions/setup-go@v5
38 |       - name: Run GoReleaser
39 |         uses: goreleaser/goreleaser-action@v6
40 |         with:
41 |           distribution: goreleaser
42 |           version: latest
43 |           args: release --clean
44 |         env:
45 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | 
47 |   docker:
48 |     needs: release-please
49 |     if: ${{ needs.release-please.outputs.release_created }}
50 |     runs-on: ubuntu-latest
51 |     steps:
52 |       - name: Checkout
53 |         uses: actions/checkout@v4
54 |         
55 |       - name: Set up Docker Buildx
56 |         uses: docker/setup-buildx-action@v3
57 | 
58 |       - name: Login to GitHub Container Registry
59 |         uses: docker/login-action@v3
60 |         with:
61 |           registry: ghcr.io
62 |           username: ${{ github.repository_owner }}
63 |           password: ${{ secrets.GITHUB_TOKEN }}
64 | 
65 |       - name: Extract metadata for Docker
66 |         id: meta
67 |         uses: docker/metadata-action@v5
68 |         with:
69 |           images: ghcr.io/${{ github.repository }}
70 |           tags: |
71 |             type=semver,pattern={{version}},value=${{ needs.release-please.outputs.tag_name }}
72 |             type=semver,pattern={{major}}.{{minor}},value=${{ needs.release-please.outputs.tag_name }}
73 |             type=semver,pattern={{major}},value=${{ needs.release-please.outputs.tag_name }}
74 |             type=raw,value=latest
75 | 
76 |       - name: Build and push Docker image
77 |         uses: docker/build-push-action@v6
78 |         with:
79 |           context: .
80 |           platforms: linux/amd64,linux/arm64
81 |           push: true
82 |           tags: ${{ steps.meta.outputs.tags }}
83 |           labels: ${{ steps.meta.outputs.labels }}
84 |           provenance: false
85 |           sbom: false
86 |           cache-from: type=gha
87 |           cache-to: type=gha,mode=max
88 | 
```

--------------------------------------------------------------------------------
/.claude/commands/speckit.plan.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | description: Execute the implementation planning workflow using the plan template to generate design artifacts.
 3 | ---
 4 | 
 5 | ## User Input
 6 | 
 7 | ```text
 8 | $ARGUMENTS
 9 | ```
10 | 
11 | You **MUST** consider the user input before proceeding (if not empty).
12 | 
13 | ## Outline
14 | 
15 | 1. **Setup**: Run `.specify/scripts/bash/setup-plan.sh --json` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH.
16 | 
17 | 2. **Load context**: Read FEATURE_SPEC and `.specify.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied).
18 | 
19 | 3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
20 |    - Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")
21 |    - Fill Constitution Check section from constitution
22 |    - Evaluate gates (ERROR if violations unjustified)
23 |    - Phase 0: Generate research.md (resolve all NEEDS CLARIFICATION)
24 |    - Phase 1: Generate data-model.md, contracts/, quickstart.md
25 |    - Phase 1: Update agent context by running the agent script
26 |    - Re-evaluate Constitution Check post-design
27 | 
28 | 4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
29 | 
30 | ## Phases
31 | 
32 | ### Phase 0: Outline & Research
33 | 
34 | 1. **Extract unknowns from Technical Context** above:
35 |    - For each NEEDS CLARIFICATION → research task
36 |    - For each dependency → best practices task
37 |    - For each integration → patterns task
38 | 
39 | 2. **Generate and dispatch research agents**:
40 |    ```
41 |    For each unknown in Technical Context:
42 |      Task: "Research {unknown} for {feature context}"
43 |    For each technology choice:
44 |      Task: "Find best practices for {tech} in {domain}"
45 |    ```
46 | 
47 | 3. **Consolidate findings** in `research.md` using format:
48 |    - Decision: [what was chosen]
49 |    - Rationale: [why chosen]
50 |    - Alternatives considered: [what else evaluated]
51 | 
52 | **Output**: research.md with all NEEDS CLARIFICATION resolved
53 | 
54 | ### Phase 1: Design & Contracts
55 | 
56 | **Prerequisites:** `research.md` complete
57 | 
58 | 1. **Extract entities from feature spec** → `data-model.md`:
59 |    - Entity name, fields, relationships
60 |    - Validation rules from requirements
61 |    - State transitions if applicable
62 | 
63 | 2. **Generate API contracts** from functional requirements:
64 |    - For each user action → endpoint
65 |    - Use standard REST/GraphQL patterns
66 |    - Output OpenAPI/GraphQL schema to `/contracts/`
67 | 
68 | 3. **Agent context update**:
69 |    - Run `.specify/scripts/bash/update-agent-context.sh claude`
70 |    - These scripts detect which AI agent is in use
71 |    - Update the appropriate agent-specific context file
72 |    - Add only new technology from current plan
73 |    - Preserve manual additions between markers
74 | 
75 | **Output**: data-model.md, /contracts/*, quickstart.md, agent-specific file
76 | 
77 | ## Key rules
78 | 
79 | - Use absolute paths
80 | - ERROR on gate failures or unresolved clarifications
81 | 
```

--------------------------------------------------------------------------------
/.specify/scripts/bash/create-new-feature.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/usr/bin/env bash
 2 | 
 3 | set -e
 4 | 
 5 | JSON_MODE=false
 6 | ARGS=()
 7 | for arg in "$@"; do
 8 |     case "$arg" in
 9 |         --json) JSON_MODE=true ;;
10 |         --help|-h) echo "Usage: $0 [--json] <feature_description>"; exit 0 ;;
11 |         *) ARGS+=("$arg") ;;
12 |     esac
13 | done
14 | 
15 | FEATURE_DESCRIPTION="${ARGS[*]}"
16 | if [ -z "$FEATURE_DESCRIPTION" ]; then
17 |     echo "Usage: $0 [--json] <feature_description>" >&2
18 |     exit 1
19 | fi
20 | 
21 | # Function to find the repository root by searching for existing project markers
22 | find_repo_root() {
23 |     local dir="$1"
24 |     while [ "$dir" != "/" ]; do
25 |         if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
26 |             echo "$dir"
27 |             return 0
28 |         fi
29 |         dir="$(dirname "$dir")"
30 |     done
31 |     return 1
32 | }
33 | 
34 | # Resolve repository root. Prefer git information when available, but fall back
35 | # to searching for repository markers so the workflow still functions in repositories that
36 | # were initialised with --no-git.
37 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38 | 
39 | if git rev-parse --show-toplevel >/dev/null 2>&1; then
40 |     REPO_ROOT=$(git rev-parse --show-toplevel)
41 |     HAS_GIT=true
42 | else
43 |     REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
44 |     if [ -z "$REPO_ROOT" ]; then
45 |         echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
46 |         exit 1
47 |     fi
48 |     HAS_GIT=false
49 | fi
50 | 
51 | cd "$REPO_ROOT"
52 | 
53 | SPECS_DIR="$REPO_ROOT/specs"
54 | mkdir -p "$SPECS_DIR"
55 | 
56 | HIGHEST=0
57 | if [ -d "$SPECS_DIR" ]; then
58 |     for dir in "$SPECS_DIR"/*; do
59 |         [ -d "$dir" ] || continue
60 |         dirname=$(basename "$dir")
61 |         number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
62 |         number=$((10#$number))
63 |         if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
64 |     done
65 | fi
66 | 
67 | NEXT=$((HIGHEST + 1))
68 | FEATURE_NUM=$(printf "%03d" "$NEXT")
69 | 
70 | BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
71 | WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
72 | BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
73 | 
74 | if [ "$HAS_GIT" = true ]; then
75 |     git checkout -b "$BRANCH_NAME"
76 | else
77 |     >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
78 | fi
79 | 
80 | FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
81 | mkdir -p "$FEATURE_DIR"
82 | 
83 | TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
84 | SPEC_FILE="$FEATURE_DIR/spec.md"
85 | if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
86 | 
87 | # Set the SPECIFY_FEATURE environment variable for the current session
88 | export SPECIFY_FEATURE="$BRANCH_NAME"
89 | 
90 | if $JSON_MODE; then
91 |     printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
92 | else
93 |     echo "BRANCH_NAME: $BRANCH_NAME"
94 |     echo "SPEC_FILE: $SPEC_FILE"
95 |     echo "FEATURE_NUM: $FEATURE_NUM"
96 |     echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
97 | fi
98 | 
```

--------------------------------------------------------------------------------
/.specify/scripts/bash/common.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/usr/bin/env bash
  2 | # Common functions and variables for all scripts
  3 | 
  4 | # Get repository root, with fallback for non-git repositories
  5 | get_repo_root() {
  6 |     if git rev-parse --show-toplevel >/dev/null 2>&1; then
  7 |         git rev-parse --show-toplevel
  8 |     else
  9 |         # Fall back to script location for non-git repos
 10 |         local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 11 |         (cd "$script_dir/../../.." && pwd)
 12 |     fi
 13 | }
 14 | 
 15 | # Get current branch, with fallback for non-git repositories
 16 | get_current_branch() {
 17 |     # First check if SPECIFY_FEATURE environment variable is set
 18 |     if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
 19 |         echo "$SPECIFY_FEATURE"
 20 |         return
 21 |     fi
 22 |     
 23 |     # Then check git if available
 24 |     if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
 25 |         git rev-parse --abbrev-ref HEAD
 26 |         return
 27 |     fi
 28 |     
 29 |     # For non-git repos, try to find the latest feature directory
 30 |     local repo_root=$(get_repo_root)
 31 |     local specs_dir="$repo_root/specs"
 32 |     
 33 |     if [[ -d "$specs_dir" ]]; then
 34 |         local latest_feature=""
 35 |         local highest=0
 36 |         
 37 |         for dir in "$specs_dir"/*; do
 38 |             if [[ -d "$dir" ]]; then
 39 |                 local dirname=$(basename "$dir")
 40 |                 if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
 41 |                     local number=${BASH_REMATCH[1]}
 42 |                     number=$((10#$number))
 43 |                     if [[ "$number" -gt "$highest" ]]; then
 44 |                         highest=$number
 45 |                         latest_feature=$dirname
 46 |                     fi
 47 |                 fi
 48 |             fi
 49 |         done
 50 |         
 51 |         if [[ -n "$latest_feature" ]]; then
 52 |             echo "$latest_feature"
 53 |             return
 54 |         fi
 55 |     fi
 56 |     
 57 |     echo "main"  # Final fallback
 58 | }
 59 | 
 60 | # Check if we have git available
 61 | has_git() {
 62 |     git rev-parse --show-toplevel >/dev/null 2>&1
 63 | }
 64 | 
 65 | check_feature_branch() {
 66 |     local branch="$1"
 67 |     local has_git_repo="$2"
 68 |     
 69 |     # For non-git repos, we can't enforce branch naming but still provide output
 70 |     if [[ "$has_git_repo" != "true" ]]; then
 71 |         echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
 72 |         return 0
 73 |     fi
 74 |     
 75 |     if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
 76 |         echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
 77 |         echo "Feature branches should be named like: 001-feature-name" >&2
 78 |         return 1
 79 |     fi
 80 |     
 81 |     return 0
 82 | }
 83 | 
 84 | get_feature_dir() { echo "$1/specs/$2"; }
 85 | 
 86 | get_feature_paths() {
 87 |     local repo_root=$(get_repo_root)
 88 |     local current_branch=$(get_current_branch)
 89 |     local has_git_repo="false"
 90 |     
 91 |     if has_git; then
 92 |         has_git_repo="true"
 93 |     fi
 94 |     
 95 |     local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
 96 |     
 97 |     cat <<EOF
 98 | REPO_ROOT='$repo_root'
 99 | CURRENT_BRANCH='$current_branch'
100 | HAS_GIT='$has_git_repo'
101 | FEATURE_DIR='$feature_dir'
102 | FEATURE_SPEC='$feature_dir/spec.md'
103 | IMPL_PLAN='$feature_dir/plan.md'
104 | TASKS='$feature_dir/tasks.md'
105 | RESEARCH='$feature_dir/research.md'
106 | DATA_MODEL='$feature_dir/data-model.md'
107 | QUICKSTART='$feature_dir/quickstart.md'
108 | CONTRACTS_DIR='$feature_dir/contracts'
109 | EOF
110 | }
111 | 
112 | check_file() { [[ -f "$1" ]] && echo "  ✓ $2" || echo "  ✗ $2"; }
113 | check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo "  ✓ $2" || echo "  ✗ $2"; }
114 | 
```

--------------------------------------------------------------------------------
/tools/jira_history.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"time"
  7 | 
  8 | 	"github.com/mark3labs/mcp-go/mcp"
  9 | 	"github.com/mark3labs/mcp-go/server"
 10 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 11 | )
 12 | 
 13 | // GetIssueHistoryInput defines the input parameters for getting issue history
 14 | type GetIssueHistoryInput struct {
 15 | 	IssueKey string `json:"issue_key" validate:"required"`
 16 | }
 17 | 
 18 | // HistoryItem represents a single change in the issue history
 19 | type HistoryItem struct {
 20 | 	Field      string `json:"field"`
 21 | 	FromString string `json:"from_string"`
 22 | 	ToString   string `json:"to_string"`
 23 | }
 24 | 
 25 | // HistoryEntry represents a single history entry with multiple changes
 26 | type HistoryEntry struct {
 27 | 	Date    string        `json:"date"`
 28 | 	Author  string        `json:"author"`
 29 | 	Changes []HistoryItem `json:"changes"`
 30 | }
 31 | 
 32 | // GetIssueHistoryOutput defines the output structure for issue history
 33 | type GetIssueHistoryOutput struct {
 34 | 	IssueKey string         `json:"issue_key"`
 35 | 	History  []HistoryEntry `json:"history"`
 36 | 	Count    int            `json:"count"`
 37 | }
 38 | 
 39 | func RegisterJiraHistoryTool(s *server.MCPServer) {
 40 | 	jiraGetIssueHistoryTool := mcp.NewTool("jira_get_issue_history",
 41 | 		mcp.WithDescription("Retrieve the complete change history of a Jira issue"),
 42 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")),
 43 | 	)
 44 | 	s.AddTool(jiraGetIssueHistoryTool, mcp.NewTypedToolHandler(jiraGetIssueHistoryHandler))
 45 | }
 46 | 
 47 | func jiraGetIssueHistoryHandler(ctx context.Context, request mcp.CallToolRequest, input GetIssueHistoryInput) (*mcp.CallToolResult, error) {
 48 | 	client := services.JiraClient()
 49 | 	
 50 | 	// Get issue with changelog expanded
 51 | 	issue, response, err := client.Issue.Get(ctx, input.IssueKey, nil, []string{"changelog"})
 52 | 	if err != nil {
 53 | 		if response != nil {
 54 | 			return mcp.NewToolResultError(fmt.Sprintf("failed to get issue history: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)), nil
 55 | 		}
 56 | 		return mcp.NewToolResultError(fmt.Sprintf("failed to get issue history: %v", err)), nil
 57 | 	}
 58 | 
 59 | 	if len(issue.Changelog.Histories) == 0 {
 60 | 		return mcp.NewToolResultText(fmt.Sprintf("No history found for issue %s", input.IssueKey)), nil
 61 | 	}
 62 | 
 63 | 	// Build structured output
 64 | 	var historyEntries []HistoryEntry
 65 | 	
 66 | 	// Process each history entry
 67 | 	for _, history := range issue.Changelog.Histories {
 68 | 		var formattedDate string
 69 | 		
 70 | 		// Parse the created time
 71 | 		createdTime, err := time.Parse("2006-01-02T15:04:05.999-0700", history.Created)
 72 | 		if err != nil {
 73 | 			// If parse fails, use the original string
 74 | 			formattedDate = history.Created
 75 | 		} else {
 76 | 			// Format the time in a more readable format
 77 | 			formattedDate = createdTime.Format("2006-01-02 15:04:05")
 78 | 		}
 79 | 
 80 | 		// Process change items
 81 | 		var changes []HistoryItem
 82 | 		for _, item := range history.Items {
 83 | 			fromString := item.FromString
 84 | 			if fromString == "" {
 85 | 				fromString = "(empty)"
 86 | 			}
 87 | 			
 88 | 			toString := item.ToString
 89 | 			if toString == "" {
 90 | 				toString = "(empty)"
 91 | 			}
 92 | 			
 93 | 			changes = append(changes, HistoryItem{
 94 | 				Field:      item.Field,
 95 | 				FromString: fromString,
 96 | 				ToString:   toString,
 97 | 			})
 98 | 		}
 99 | 		
100 | 		historyEntries = append(historyEntries, HistoryEntry{
101 | 			Date:    formattedDate,
102 | 			Author:  history.Author.DisplayName,
103 | 			Changes: changes,
104 | 		})
105 | 	}
106 | 
107 | 	output := GetIssueHistoryOutput{
108 | 		IssueKey: input.IssueKey,
109 | 		History:  historyEntries,
110 | 		Count:    len(historyEntries),
111 | 	}
112 | 
113 | 	return mcp.NewToolResultJSON(output)
114 | } 
```

--------------------------------------------------------------------------------
/tools/jira_comment.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 
  7 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
  8 | 	"github.com/mark3labs/mcp-go/mcp"
  9 | 	"github.com/mark3labs/mcp-go/server"
 10 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 11 | )
 12 | 
 13 | // Input types for typed tools
 14 | type AddCommentInput struct {
 15 | 	IssueKey string `json:"issue_key" validate:"required"`
 16 | 	Comment  string `json:"comment" validate:"required"`
 17 | }
 18 | 
 19 | type GetCommentsInput struct {
 20 | 	IssueKey string `json:"issue_key" validate:"required"`
 21 | }
 22 | 
 23 | func RegisterJiraCommentTools(s *server.MCPServer) {
 24 | 	jiraAddCommentTool := mcp.NewTool("jira_add_comment",
 25 | 		mcp.WithDescription("Add a comment to a Jira issue"),
 26 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")),
 27 | 		mcp.WithString("comment", mcp.Required(), mcp.Description("The comment text to add to the issue")),
 28 | 	)
 29 | 	s.AddTool(jiraAddCommentTool, mcp.NewTypedToolHandler(jiraAddCommentHandler))
 30 | 
 31 | 	jiraGetCommentsTool := mcp.NewTool("jira_get_comments",
 32 | 		mcp.WithDescription("Retrieve all comments from a Jira issue"),
 33 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")),
 34 | 	)
 35 | 	s.AddTool(jiraGetCommentsTool, mcp.NewTypedToolHandler(jiraGetCommentsHandler))
 36 | }
 37 | 
 38 | func jiraAddCommentHandler(ctx context.Context, request mcp.CallToolRequest, input AddCommentInput) (*mcp.CallToolResult, error) {
 39 | 	client := services.JiraClient()
 40 | 
 41 | 	// Create proper ADF structure: document → paragraph → text
 42 | 	commentPayload := &models.CommentPayloadScheme{
 43 | 		Body: &models.CommentNodeScheme{
 44 | 			Version: 1,
 45 | 			Type:    "doc",
 46 | 			Content: []*models.CommentNodeScheme{
 47 | 				{
 48 | 					Type: "paragraph",
 49 | 					Content: []*models.CommentNodeScheme{
 50 | 						{
 51 | 							Type: "text",
 52 | 							Text: input.Comment,
 53 | 						},
 54 | 					},
 55 | 				},
 56 | 			},
 57 | 		},
 58 | 	}
 59 | 
 60 | 	comment, response, err := client.Issue.Comment.Add(ctx, input.IssueKey, commentPayload, nil)
 61 | 	if err != nil {
 62 | 		if response != nil {
 63 | 			return nil, fmt.Errorf("failed to add comment: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 64 | 		}
 65 | 		return nil, fmt.Errorf("failed to add comment: %v", err)
 66 | 	}
 67 | 
 68 | 	result := fmt.Sprintf("Comment added successfully!\nID: %s\nAuthor: %s\nCreated: %s",
 69 | 		comment.ID,
 70 | 		comment.Author.DisplayName,
 71 | 		comment.Created)
 72 | 
 73 | 	return mcp.NewToolResultText(result), nil
 74 | }
 75 | 
 76 | func jiraGetCommentsHandler(ctx context.Context, request mcp.CallToolRequest, input GetCommentsInput) (*mcp.CallToolResult, error) {
 77 | 	client := services.JiraClient()
 78 | 
 79 | 	// Retrieve up to 50 comments starting from the first one.
 80 | 	// Passing 0 for maxResults results in Jira returning only the first comment.
 81 | 	comments, response, err := client.Issue.Comment.Gets(ctx, input.IssueKey, "", nil, 0, 50)
 82 | 	if err != nil {
 83 | 		if response != nil {
 84 | 			return nil, fmt.Errorf("failed to get comments: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 85 | 		}
 86 | 		return nil, fmt.Errorf("failed to get comments: %v", err)
 87 | 	}
 88 | 
 89 | 	if len(comments.Comments) == 0 {
 90 | 		return mcp.NewToolResultText("No comments found for this issue."), nil
 91 | 	}
 92 | 
 93 | 	var result string
 94 | 	for _, comment := range comments.Comments {
 95 | 		authorName := "Unknown"
 96 | 		if comment.Author != nil {
 97 | 			authorName = comment.Author.DisplayName
 98 | 		}
 99 | 
100 | 		result += fmt.Sprintf("ID: %s\nAuthor: %s\nCreated: %s\nUpdated: %s\nBody: %s\n\n",
101 | 			comment.ID,
102 | 			authorName,
103 | 			comment.Created,
104 | 			comment.Updated,
105 | 			comment.Body)
106 | 	}
107 | 
108 | 	return mcp.NewToolResultText(result), nil
109 | }
110 | 
```

--------------------------------------------------------------------------------
/tools/jira_worklog.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"strconv"
  7 | 	"time"
  8 | 
  9 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
 10 | 	"github.com/mark3labs/mcp-go/mcp"
 11 | 	"github.com/mark3labs/mcp-go/server"
 12 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 13 | )
 14 | 
 15 | // Input types for typed tools
 16 | type AddWorklogInput struct {
 17 | 	IssueKey  string `json:"issue_key" validate:"required"`
 18 | 	TimeSpent string `json:"time_spent" validate:"required"`
 19 | 	Comment   string `json:"comment,omitempty"`
 20 | 	Started   string `json:"started,omitempty"`
 21 | }
 22 | 
 23 | func RegisterJiraWorklogTool(s *server.MCPServer) {
 24 | 	jiraAddWorklogTool := mcp.NewTool("jira_add_worklog",
 25 | 		mcp.WithDescription("Add a worklog to a Jira issue to track time spent on the issue"),
 26 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")),
 27 | 		mcp.WithString("time_spent", mcp.Required(), mcp.Description("Time spent working on the issue (e.g., 3h, 30m, 1h 30m)")),
 28 | 		mcp.WithString("comment", mcp.Description("Comment describing the work done")),
 29 | 		mcp.WithString("started", mcp.Description("When the work began, in ISO 8601 format (e.g., 2023-05-01T10:00:00.000+0000). Defaults to current time.")),
 30 | 	)
 31 | 	s.AddTool(jiraAddWorklogTool, mcp.NewTypedToolHandler(jiraAddWorklogHandler))
 32 | }
 33 | 
 34 | func jiraAddWorklogHandler(ctx context.Context, request mcp.CallToolRequest, input AddWorklogInput) (*mcp.CallToolResult, error) {
 35 | 	client := services.JiraClient()
 36 | 
 37 | 	// Convert timeSpent to seconds (this is a simplification - in a real implementation 
 38 | 	// you would need to parse formats like "1h 30m" properly)
 39 | 	timeSpentSeconds, err := parseTimeSpent(input.TimeSpent)
 40 | 	if err != nil {
 41 | 		return nil, fmt.Errorf("invalid time_spent format: %v", err)
 42 | 	}
 43 | 
 44 | 	// Get started time if provided, otherwise use current time
 45 | 	var started string
 46 | 	if input.Started != "" {
 47 | 		started = input.Started
 48 | 	} else {
 49 | 		// Format current time in ISO 8601 format
 50 | 		started = time.Now().Format("2006-01-02T15:04:05.000-0700")
 51 | 	}
 52 | 
 53 | 	options := &models.WorklogOptionsScheme{
 54 | 		Notify:         true,
 55 | 		AdjustEstimate: "auto",
 56 | 	}
 57 | 
 58 | 	payload := &models.WorklogADFPayloadScheme{
 59 | 		TimeSpentSeconds: timeSpentSeconds,
 60 | 		Started:          started,
 61 | 	}
 62 | 
 63 | 	// Add comment if provided
 64 | 	if input.Comment != "" {
 65 | 		payload.Comment = &models.CommentNodeScheme{
 66 | 			Type: "text",
 67 | 			Text: input.Comment,
 68 | 		}
 69 | 	}
 70 | 
 71 | 	// Call the Jira API to add the worklog
 72 | 	worklog, response, err := client.Issue.Worklog.Add(ctx, input.IssueKey, payload, options)
 73 | 	if err != nil {
 74 | 		if response != nil {
 75 | 			return nil, fmt.Errorf("failed to add worklog: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 76 | 		}
 77 | 		return nil, fmt.Errorf("failed to add worklog: %v", err)
 78 | 	}
 79 | 
 80 | 	result := fmt.Sprintf(`Worklog added successfully!
 81 | Issue: %s
 82 | Worklog ID: %s
 83 | Time Spent: %s (%d seconds)
 84 | Date Started: %s
 85 | Author: %s`,
 86 | 		input.IssueKey,
 87 | 		worklog.ID,
 88 | 		input.TimeSpent,
 89 | 		worklog.TimeSpentSeconds,
 90 | 		worklog.Started,
 91 | 		worklog.Author.DisplayName,
 92 | 	)
 93 | 
 94 | 	return mcp.NewToolResultText(result), nil
 95 | }
 96 | 
 97 | // parseTimeSpent converts time formats like "3h", "30m", "1h 30m" to seconds
 98 | func parseTimeSpent(timeSpent string) (int, error) {
 99 | 	// This is a simplified version - a real implementation would be more robust
100 | 	// For this example, we'll just handle hours (h) and minutes (m)
101 | 	
102 | 	// Simple case: if it's just a number, treat it as seconds
103 | 	seconds, err := strconv.Atoi(timeSpent)
104 | 	if err == nil {
105 | 		return seconds, nil
106 | 	}
107 | 
108 | 	// Otherwise, try to parse as a duration
109 | 	duration, err := time.ParseDuration(timeSpent)
110 | 	if err == nil {
111 | 		return int(duration.Seconds()), nil
112 | 	}
113 | 
114 | 	// If all else fails, return an error
115 | 	return 0, fmt.Errorf("could not parse time: %s", timeSpent)
116 | } 
```

--------------------------------------------------------------------------------
/prompts/jira_prompts.go:
--------------------------------------------------------------------------------

```go
 1 | package prompts
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/mark3labs/mcp-go/mcp"
 8 | 	"github.com/mark3labs/mcp-go/server"
 9 | )
10 | 
11 | // RegisterJiraPrompts registers all Jira-related prompts with the MCP server
12 | func RegisterJiraPrompts(s *server.MCPServer) {
13 | 	// Prompt 1: List all development work for an issue and its subtasks
14 | 	s.AddPrompt(mcp.NewPrompt("issue_development_tree",
15 | 		mcp.WithPromptDescription("List all development work (branches, PRs, commits) for a Jira issue and all its child issues/subtasks"),
16 | 		mcp.WithArgument("issue_key",
17 | 			mcp.ArgumentDescription("The Jira issue key to analyze (e.g., PROJ-123)"),
18 | 			mcp.RequiredArgument(),
19 | 		),
20 | 	), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
21 | 		issueKey := request.Params.Arguments["issue_key"]
22 | 		if issueKey == "" {
23 | 			return nil, fmt.Errorf("issue_key is required")
24 | 		}
25 | 
26 | 		return mcp.NewGetPromptResult(
27 | 			"Development work tree for issue and subtasks",
28 | 			[]mcp.PromptMessage{
29 | 				mcp.NewPromptMessage(
30 | 					mcp.RoleUser,
31 | 					mcp.NewTextContent(fmt.Sprintf(`Please analyze all development work for issue %s and its child issues:
32 | 
33 | 1. First, use jira_get_issue with issue_key=%s and expand=subtasks to retrieve the parent issue and all its subtasks
34 | 2. Then, use jira_get_development_information to get branches, pull requests, and commits for the parent issue %s
35 | 3. For each subtask found, call jira_get_development_information to get their development work
36 | 4. Format the results as a hierarchical tree showing:
37 |    - Parent issue: %s
38 |      - Development work (branches, PRs, commits)
39 |    - Each subtask:
40 |      - Development work (branches, PRs, commits)
41 | 
42 | Please provide a clear summary of all development activity across the entire issue tree.`, issueKey, issueKey, issueKey, issueKey)),
43 | 				),
44 | 			},
45 | 		), nil
46 | 	})
47 | 
48 | 	// Prompt 2: List all issues and their development work for a release/version
49 | 	s.AddPrompt(mcp.NewPrompt("release_development_overview",
50 | 		mcp.WithPromptDescription("List all issues and their development work (branches, PRs, commits) for a specific release/version"),
51 | 		mcp.WithArgument("version",
52 | 			mcp.ArgumentDescription("The version/release name (e.g., v1.0.0, Sprint 23)"),
53 | 			mcp.RequiredArgument(),
54 | 		),
55 | 		mcp.WithArgument("project_key",
56 | 			mcp.ArgumentDescription("The Jira project key (e.g., PROJ, KP)"),
57 | 			mcp.RequiredArgument(),
58 | 		),
59 | 	), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
60 | 		version := request.Params.Arguments["version"]
61 | 		projectKey := request.Params.Arguments["project_key"]
62 | 
63 | 		if version == "" {
64 | 			return nil, fmt.Errorf("version is required")
65 | 		}
66 | 		if projectKey == "" {
67 | 			return nil, fmt.Errorf("project_key is required")
68 | 		}
69 | 
70 | 		return mcp.NewGetPromptResult(
71 | 			"Development overview for release",
72 | 			[]mcp.PromptMessage{
73 | 				mcp.NewPromptMessage(
74 | 					mcp.RoleUser,
75 | 					mcp.NewTextContent(fmt.Sprintf(`Please provide a comprehensive development overview for release "%s" in project %s:
76 | 
77 | 1. First, use jira_search_issue with JQL: fixVersion = "%s" AND project = %s
78 | 2. For each issue found in the search results, call jira_get_development_information to retrieve:
79 |    - Branches associated with the issue
80 |    - Pull requests (status, reviewers, etc.)
81 |    - Commits and code changes
82 | 3. Organize the results by issue and provide a summary that includes:
83 |    - Total number of issues in the release
84 |    - List each issue with its key, summary, and status
85 |    - Development work for each issue (branches, PRs, commits)
86 |    - Overall statistics (total PRs, merged PRs, open branches, etc.)
87 | 
88 | Please format the output clearly so it's easy to review the entire release's development status.`, version, projectKey, version, projectKey)),
89 | 				),
90 | 			},
91 | 		), nil
92 | 	})
93 | }
94 | 
```

--------------------------------------------------------------------------------
/tools/jira_version.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"strings"
  7 | 
  8 | 	"github.com/mark3labs/mcp-go/mcp"
  9 | 	"github.com/mark3labs/mcp-go/server"
 10 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 11 | )
 12 | 
 13 | // Input types for version tools
 14 | type GetVersionInput struct {
 15 | 	VersionID string `json:"version_id" validate:"required"`
 16 | }
 17 | 
 18 | type ListProjectVersionsInput struct {
 19 | 	ProjectKey string `json:"project_key" validate:"required"`
 20 | }
 21 | 
 22 | func RegisterJiraVersionTool(s *server.MCPServer) {
 23 | 	jiraGetVersionTool := mcp.NewTool("jira_get_version",
 24 | 		mcp.WithDescription("Retrieve detailed information about a specific Jira project version including its name, description, release date, and status"),
 25 | 		mcp.WithString("version_id", mcp.Required(), mcp.Description("The unique identifier of the version to retrieve (e.g., 10000)")),
 26 | 	)
 27 | 	s.AddTool(jiraGetVersionTool, mcp.NewTypedToolHandler(jiraGetVersionHandler))
 28 | 
 29 | 	jiraListProjectVersionsTool := mcp.NewTool("jira_list_project_versions",
 30 | 		mcp.WithDescription("List all versions in a Jira project with their details including names, descriptions, release dates, and statuses"),
 31 | 		mcp.WithString("project_key", mcp.Required(), mcp.Description("Project identifier to list versions for (e.g., KP, PROJ)")),
 32 | 	)
 33 | 	s.AddTool(jiraListProjectVersionsTool, mcp.NewTypedToolHandler(jiraListProjectVersionsHandler))
 34 | }
 35 | 
 36 | func jiraGetVersionHandler(ctx context.Context, request mcp.CallToolRequest, input GetVersionInput) (*mcp.CallToolResult, error) {
 37 | 	client := services.JiraClient()
 38 | 
 39 | 	version, response, err := client.Project.Version.Get(ctx, input.VersionID, nil)
 40 | 	if err != nil {
 41 | 		if response != nil {
 42 | 			return nil, fmt.Errorf("failed to get version: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 43 | 		}
 44 | 		return nil, fmt.Errorf("failed to get version: %v", err)
 45 | 	}
 46 | 
 47 | 	var result strings.Builder
 48 | 	result.WriteString(fmt.Sprintf("Version Details:\n\n"))
 49 | 	result.WriteString(fmt.Sprintf("ID: %s\n", version.ID))
 50 | 	result.WriteString(fmt.Sprintf("Name: %s\n", version.Name))
 51 | 
 52 | 	if version.Description != "" {
 53 | 		result.WriteString(fmt.Sprintf("Description: %s\n", version.Description))
 54 | 	}
 55 | 
 56 | 	if version.ProjectID != 0 {
 57 | 		result.WriteString(fmt.Sprintf("Project ID: %d\n", version.ProjectID))
 58 | 	}
 59 | 
 60 | 	result.WriteString(fmt.Sprintf("Released: %t\n", version.Released))
 61 | 	result.WriteString(fmt.Sprintf("Archived: %t\n", version.Archived))
 62 | 
 63 | 	if version.ReleaseDate != "" {
 64 | 		result.WriteString(fmt.Sprintf("Release Date: %s\n", version.ReleaseDate))
 65 | 	}
 66 | 
 67 | 	if version.Self != "" {
 68 | 		result.WriteString(fmt.Sprintf("URL: %s\n", version.Self))
 69 | 	}
 70 | 
 71 | 	return mcp.NewToolResultText(result.String()), nil
 72 | }
 73 | 
 74 | func jiraListProjectVersionsHandler(ctx context.Context, request mcp.CallToolRequest, input ListProjectVersionsInput) (*mcp.CallToolResult, error) {
 75 | 	client := services.JiraClient()
 76 | 
 77 | 	versions, response, err := client.Project.Version.Gets(ctx, input.ProjectKey)
 78 | 	if err != nil {
 79 | 		if response != nil {
 80 | 			return nil, fmt.Errorf("failed to list project versions: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 81 | 		}
 82 | 		return nil, fmt.Errorf("failed to list project versions: %v", err)
 83 | 	}
 84 | 
 85 | 	if len(versions) == 0 {
 86 | 		return mcp.NewToolResultText(fmt.Sprintf("No versions found for project %s.", input.ProjectKey)), nil
 87 | 	}
 88 | 
 89 | 	var result strings.Builder
 90 | 	result.WriteString(fmt.Sprintf("Project %s Versions:\n\n", input.ProjectKey))
 91 | 
 92 | 	for i, version := range versions {
 93 | 		if i > 0 {
 94 | 			result.WriteString("\n")
 95 | 		}
 96 | 
 97 | 		result.WriteString(fmt.Sprintf("ID: %s\n", version.ID))
 98 | 		result.WriteString(fmt.Sprintf("Name: %s\n", version.Name))
 99 | 
100 | 		if version.Description != "" {
101 | 			result.WriteString(fmt.Sprintf("Description: %s\n", version.Description))
102 | 		}
103 | 
104 | 		status := "In Development"
105 | 		if version.Released {
106 | 			status = "Released"
107 | 		}
108 | 		if version.Archived {
109 | 			status = "Archived"
110 | 		}
111 | 		result.WriteString(fmt.Sprintf("Status: %s\n", status))
112 | 
113 | 		if version.ReleaseDate != "" {
114 | 			result.WriteString(fmt.Sprintf("Release Date: %s\n", version.ReleaseDate))
115 | 		}
116 | 
117 | 		if version.Released {
118 | 			result.WriteString(fmt.Sprintf("Start Date: %t\n", version.Released))
119 | 		}
120 | 	}
121 | 
122 | 	return mcp.NewToolResultText(result.String()), nil
123 | }
124 | 
```

--------------------------------------------------------------------------------
/.specify/templates/spec-template.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Feature Specification: [FEATURE NAME]
  2 | 
  3 | **Feature Branch**: `[###-feature-name]`  
  4 | **Created**: [DATE]  
  5 | **Status**: Draft  
  6 | **Input**: User description: "$ARGUMENTS"
  7 | 
  8 | ## User Scenarios & Testing *(mandatory)*
  9 | 
 10 | <!--
 11 |   IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
 12 |   Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
 13 |   you should still have a viable MVP (Minimum Viable Product) that delivers value.
 14 |   
 15 |   Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
 16 |   Think of each story as a standalone slice of functionality that can be:
 17 |   - Developed independently
 18 |   - Tested independently
 19 |   - Deployed independently
 20 |   - Demonstrated to users independently
 21 | -->
 22 | 
 23 | ### User Story 1 - [Brief Title] (Priority: P1)
 24 | 
 25 | [Describe this user journey in plain language]
 26 | 
 27 | **Why this priority**: [Explain the value and why it has this priority level]
 28 | 
 29 | **Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]
 30 | 
 31 | **Acceptance Scenarios**:
 32 | 
 33 | 1. **Given** [initial state], **When** [action], **Then** [expected outcome]
 34 | 2. **Given** [initial state], **When** [action], **Then** [expected outcome]
 35 | 
 36 | ---
 37 | 
 38 | ### User Story 2 - [Brief Title] (Priority: P2)
 39 | 
 40 | [Describe this user journey in plain language]
 41 | 
 42 | **Why this priority**: [Explain the value and why it has this priority level]
 43 | 
 44 | **Independent Test**: [Describe how this can be tested independently]
 45 | 
 46 | **Acceptance Scenarios**:
 47 | 
 48 | 1. **Given** [initial state], **When** [action], **Then** [expected outcome]
 49 | 
 50 | ---
 51 | 
 52 | ### User Story 3 - [Brief Title] (Priority: P3)
 53 | 
 54 | [Describe this user journey in plain language]
 55 | 
 56 | **Why this priority**: [Explain the value and why it has this priority level]
 57 | 
 58 | **Independent Test**: [Describe how this can be tested independently]
 59 | 
 60 | **Acceptance Scenarios**:
 61 | 
 62 | 1. **Given** [initial state], **When** [action], **Then** [expected outcome]
 63 | 
 64 | ---
 65 | 
 66 | [Add more user stories as needed, each with an assigned priority]
 67 | 
 68 | ### Edge Cases
 69 | 
 70 | <!--
 71 |   ACTION REQUIRED: The content in this section represents placeholders.
 72 |   Fill them out with the right edge cases.
 73 | -->
 74 | 
 75 | - What happens when [boundary condition]?
 76 | - How does system handle [error scenario]?
 77 | 
 78 | ## Requirements *(mandatory)*
 79 | 
 80 | <!--
 81 |   ACTION REQUIRED: The content in this section represents placeholders.
 82 |   Fill them out with the right functional requirements.
 83 | 
 84 |   For Jira MCP context: Requirements should specify MCP tools, parameters, and expected output.
 85 |   Example: "System MUST expose jira_create_issue tool accepting project_key, summary, description"
 86 | -->
 87 | 
 88 | ### Functional Requirements
 89 | 
 90 | - **FR-001**: System MUST [specific MCP tool capability, e.g., "expose jira_<operation> tool"]
 91 | - **FR-002**: Tool MUST [input validation, e.g., "validate required parameters via typed input struct"]
 92 | - **FR-003**: Tool MUST [output format, e.g., "return human-readable text formatted for LLM consumption"]
 93 | - **FR-004**: Tool MUST [error handling, e.g., "include endpoint and response body in error messages"]
 94 | - **FR-005**: System MUST [resource behavior, e.g., "reuse singleton Jira client connection"]
 95 | 
 96 | *Example of marking unclear requirements:*
 97 | 
 98 | - **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
 99 | - **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
100 | 
101 | ### Key Entities *(include if feature involves data)*
102 | 
103 | - **[Entity 1]**: [What it represents, key attributes without implementation]
104 | - **[Entity 2]**: [What it represents, relationships to other entities]
105 | 
106 | ## Success Criteria *(mandatory)*
107 | 
108 | <!--
109 |   ACTION REQUIRED: Define measurable success criteria.
110 |   These must be technology-agnostic and measurable.
111 | -->
112 | 
113 | ### Measurable Outcomes
114 | 
115 | - **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
116 | - **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
117 | - **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
118 | - **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]
119 | 
```

--------------------------------------------------------------------------------
/tools/jira_search.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"encoding/json"
  6 | 	"fmt"
  7 | 	"net/http"
  8 | 	"net/url"
  9 | 	"strconv"
 10 | 	"strings"
 11 | 
 12 | 	"github.com/mark3labs/mcp-go/mcp"
 13 | 	"github.com/mark3labs/mcp-go/server"
 14 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 15 | 	"github.com/nguyenvanduocit/jira-mcp/util"
 16 | 	jira "github.com/ctreminiom/go-atlassian/jira/v3"
 17 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
 18 | )
 19 | 
 20 | // Input types for typed tools
 21 | type SearchIssueInput struct {
 22 | 	JQL    string `json:"jql" validate:"required"`
 23 | 	Fields string `json:"fields,omitempty"`
 24 | 	Expand string `json:"expand,omitempty"`
 25 | }
 26 | 
 27 | // searchIssuesJQL performs JQL search using the new /rest/api/3/search/jql endpoint
 28 | func searchIssuesJQL(ctx context.Context, client *jira.Client, jql string, fields []string, expand []string, startAt, maxResults int) (*models.IssueSearchScheme, error) {
 29 | 	// Prepare query parameters
 30 | 	params := url.Values{}
 31 | 	params.Set("jql", jql)
 32 | 
 33 | 	if len(fields) > 0 {
 34 | 		params.Set("fields", strings.Join(fields, ","))
 35 | 	}
 36 | 
 37 | 	if len(expand) > 0 {
 38 | 		params.Set("expand", strings.Join(expand, ","))
 39 | 	}
 40 | 
 41 | 	if startAt > 0 {
 42 | 		params.Set("startAt", strconv.Itoa(startAt))
 43 | 	}
 44 | 
 45 | 	if maxResults > 0 {
 46 | 		params.Set("maxResults", strconv.Itoa(maxResults))
 47 | 	}
 48 | 
 49 | 	// Build the URL
 50 | 	endpoint := fmt.Sprintf("%s/rest/api/3/search/jql?%s", client.Site.String(), params.Encode())
 51 | 
 52 | 	// Create HTTP request
 53 | 	req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
 54 | 	if err != nil {
 55 | 		return nil, fmt.Errorf("failed to create request: %w", err)
 56 | 	}
 57 | 
 58 | 	// Add authentication headers - the client already has basic auth configured
 59 | 	if client.Auth != nil && client.Auth.HasBasicAuth() {
 60 | 		username, password := client.Auth.GetBasicAuth()
 61 | 		req.SetBasicAuth(username, password)
 62 | 	}
 63 | 
 64 | 	req.Header.Set("Accept", "application/json")
 65 | 
 66 | 	// Perform the request
 67 | 	resp, err := client.HTTP.Do(req)
 68 | 	if err != nil {
 69 | 		return nil, fmt.Errorf("failed to perform request: %w", err)
 70 | 	}
 71 | 	defer resp.Body.Close()
 72 | 
 73 | 	if resp.StatusCode != http.StatusOK {
 74 | 		return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, resp.Status)
 75 | 	}
 76 | 
 77 | 	// Parse the response
 78 | 	var searchResult models.IssueSearchScheme
 79 | 	if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
 80 | 		return nil, fmt.Errorf("failed to decode response: %w", err)
 81 | 	}
 82 | 
 83 | 	return &searchResult, nil
 84 | }
 85 | 
 86 | func RegisterJiraSearchTool(s *server.MCPServer) {
 87 | 	jiraSearchTool := mcp.NewTool("jira_search_issue",
 88 | 		mcp.WithDescription("Search for Jira issues using JQL (Jira Query Language). Returns key details like summary, status, assignee, and priority for matching issues"),
 89 | 		mcp.WithString("jql", mcp.Required(), mcp.Description("JQL query string (e.g., 'project = SHTP AND status = \"In Progress\"')")),
 90 | 		mcp.WithString("fields", mcp.Description("Comma-separated list of fields to retrieve (e.g., 'summary,status,assignee'). If not specified, all fields are returned.")),
 91 | 		mcp.WithString("expand", mcp.Description("Comma-separated list of fields to expand for additional details (e.g., 'transitions,changelog,subtasks,description').")),
 92 | 	)
 93 | 	s.AddTool(jiraSearchTool, mcp.NewTypedToolHandler(jiraSearchHandler))
 94 | }
 95 | 
 96 | func jiraSearchHandler(ctx context.Context, request mcp.CallToolRequest, input SearchIssueInput) (*mcp.CallToolResult, error) {
 97 | 	client := services.JiraClient()
 98 | 
 99 | 	// Parse fields parameter
100 | 	var fields []string
101 | 	if input.Fields != "" {
102 | 		fields = strings.Split(strings.ReplaceAll(input.Fields, " ", ""), ",")
103 | 	}
104 | 
105 | 	// Parse expand parameter
106 | 	var expand []string = []string{"transitions", "changelog", "subtasks", "description"}
107 | 	if input.Expand != "" {
108 | 		expand = strings.Split(strings.ReplaceAll(input.Expand, " ", ""), ",")
109 | 	}
110 | 	
111 | 	searchResult, err := searchIssuesJQL(ctx, client, input.JQL, fields, expand, 0, 30)
112 | 	if err != nil {
113 | 		return nil, fmt.Errorf("failed to search issues: %v", err)
114 | 	}
115 | 
116 | 	if len(searchResult.Issues) == 0 {
117 | 		return mcp.NewToolResultText("No issues found matching the search criteria."), nil
118 | 	}
119 | 
120 | 	var sb strings.Builder	
121 | 	for index, issue := range searchResult.Issues {
122 | 		// Use the comprehensive formatter for each issue
123 | 		formattedIssue := util.FormatJiraIssue(issue)
124 | 		sb.WriteString(formattedIssue)
125 | 		if index < len(searchResult.Issues) - 1 {
126 | 			sb.WriteString("\n===\n")
127 | 		}
128 | 	}
129 | 
130 | 	return mcp.NewToolResultText(sb.String()), nil
131 | }
132 | 
```

--------------------------------------------------------------------------------
/.specify/templates/plan-template.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Implementation Plan: [FEATURE]
  2 | 
  3 | **Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
  4 | **Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
  5 | 
  6 | **Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
  7 | 
  8 | ## Summary
  9 | 
 10 | [Extract from feature spec: primary requirement + technical approach from research]
 11 | 
 12 | ## Technical Context
 13 | 
 14 | <!--
 15 |   ACTION REQUIRED: Replace the content in this section with the technical details
 16 |   for the project. The structure here is presented in advisory capacity to guide
 17 |   the iteration process.
 18 | -->
 19 | 
 20 | **Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]  
 21 | **Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]  
 22 | **Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]  
 23 | **Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]  
 24 | **Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
 25 | **Project Type**: [single/web/mobile - determines source structure]  
 26 | **Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]  
 27 | **Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]  
 28 | **Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]
 29 | 
 30 | ## Constitution Check
 31 | 
 32 | *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
 33 | 
 34 | **MCP Protocol Compliance:**
 35 | - [ ] All features exposed via MCP tools (no direct API bypass)
 36 | - [ ] Tool names follow `jira_<operation>` convention
 37 | - [ ] STDIO mode primary, HTTP mode development-only
 38 | 
 39 | **AI-First Output:**
 40 | - [ ] Responses formatted for LLM readability
 41 | - [ ] Error messages include diagnostic context (endpoint, response body)
 42 | - [ ] Output uses `util.Format*` functions or similar formatting
 43 | 
 44 | **Simplicity:**
 45 | - [ ] No unnecessary abstraction layers (managers, facades, orchestrators)
 46 | - [ ] Direct client calls preferred over wrappers
 47 | - [ ] Complexity violations documented in "Complexity Tracking" below
 48 | 
 49 | **Type Safety:**
 50 | - [ ] Input structs defined with JSON tags and validation
 51 | - [ ] Typed handlers used (`mcp.NewTypedToolHandler`)
 52 | 
 53 | **Resource Efficiency:**
 54 | - [ ] Singleton pattern for client connections (`services.JiraClient()`)
 55 | - [ ] No per-request client creation
 56 | 
 57 | **Testing Gates:**
 58 | - [ ] Integration tests for new tool categories
 59 | - [ ] Contract tests for tool registration and parameters
 60 | 
 61 | ## Project Structure
 62 | 
 63 | ### Documentation (this feature)
 64 | 
 65 | ```
 66 | specs/[###-feature]/
 67 | ├── plan.md              # This file (/speckit.plan command output)
 68 | ├── research.md          # Phase 0 output (/speckit.plan command)
 69 | ├── data-model.md        # Phase 1 output (/speckit.plan command)
 70 | ├── quickstart.md        # Phase 1 output (/speckit.plan command)
 71 | ├── contracts/           # Phase 1 output (/speckit.plan command)
 72 | └── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
 73 | ```
 74 | 
 75 | ### Source Code (repository root)
 76 | <!--
 77 |   ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
 78 |   for this feature. Delete unused options and expand the chosen structure with
 79 |   real paths (e.g., apps/admin, packages/something). The delivered plan must
 80 |   not include Option labels.
 81 | -->
 82 | 
 83 | ```
 84 | # [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
 85 | src/
 86 | ├── models/
 87 | ├── services/
 88 | ├── cli/
 89 | └── lib/
 90 | 
 91 | tests/
 92 | ├── contract/
 93 | ├── integration/
 94 | └── unit/
 95 | 
 96 | # [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
 97 | backend/
 98 | ├── src/
 99 | │   ├── models/
100 | │   ├── services/
101 | │   └── api/
102 | └── tests/
103 | 
104 | frontend/
105 | ├── src/
106 | │   ├── components/
107 | │   ├── pages/
108 | │   └── services/
109 | └── tests/
110 | 
111 | # [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
112 | api/
113 | └── [same as backend above]
114 | 
115 | ios/ or android/
116 | └── [platform-specific structure: feature modules, UI flows, platform tests]
117 | ```
118 | 
119 | **Structure Decision**: [Document the selected structure and reference the real
120 | directories captured above]
121 | 
122 | ## Complexity Tracking
123 | 
124 | *Fill ONLY if Constitution Check has violations that must be justified*
125 | 
126 | | Violation | Why Needed | Simpler Alternative Rejected Because |
127 | |-----------|------------|-------------------------------------|
128 | | [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
129 | | [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
130 | 
```

--------------------------------------------------------------------------------
/.claude/commands/speckit.implement.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | description: Execute the implementation plan by processing and executing all tasks defined in tasks.md
 3 | ---
 4 | 
 5 | ## User Input
 6 | 
 7 | ```text
 8 | $ARGUMENTS
 9 | ```
10 | 
11 | You **MUST** consider the user input before proceeding (if not empty).
12 | 
13 | ## Outline
14 | 
15 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
16 | 
17 | 2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
18 |    - Scan all checklist files in the checklists/ directory
19 |    - For each checklist, count:
20 |      * Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
21 |      * Completed items: Lines matching `- [X]` or `- [x]`
22 |      * Incomplete items: Lines matching `- [ ]`
23 |    - Create a status table:
24 |      ```
25 |      | Checklist | Total | Completed | Incomplete | Status |
26 |      |-----------|-------|-----------|------------|--------|
27 |      | ux.md     | 12    | 12        | 0          | ✓ PASS |
28 |      | test.md   | 8     | 5         | 3          | ✗ FAIL |
29 |      | security.md | 6   | 6         | 0          | ✓ PASS |
30 |      ```
31 |    - Calculate overall status:
32 |      * **PASS**: All checklists have 0 incomplete items
33 |      * **FAIL**: One or more checklists have incomplete items
34 |    
35 |    - **If any checklist is incomplete**:
36 |      * Display the table with incomplete item counts
37 |      * **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
38 |      * Wait for user response before continuing
39 |      * If user says "no" or "wait" or "stop", halt execution
40 |      * If user says "yes" or "proceed" or "continue", proceed to step 3
41 |    
42 |    - **If all checklists are complete**:
43 |      * Display the table showing all checklists passed
44 |      * Automatically proceed to step 3
45 | 
46 | 3. Load and analyze the implementation context:
47 |    - **REQUIRED**: Read tasks.md for the complete task list and execution plan
48 |    - **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
49 |    - **IF EXISTS**: Read data-model.md for entities and relationships
50 |    - **IF EXISTS**: Read contracts/ for API specifications and test requirements
51 |    - **IF EXISTS**: Read research.md for technical decisions and constraints
52 |    - **IF EXISTS**: Read quickstart.md for integration scenarios
53 | 
54 | 4. Parse tasks.md structure and extract:
55 |    - **Task phases**: Setup, Tests, Core, Integration, Polish
56 |    - **Task dependencies**: Sequential vs parallel execution rules
57 |    - **Task details**: ID, description, file paths, parallel markers [P]
58 |    - **Execution flow**: Order and dependency requirements
59 | 
60 | 5. Execute implementation following the task plan:
61 |    - **Phase-by-phase execution**: Complete each phase before moving to the next
62 |    - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together  
63 |    - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
64 |    - **File-based coordination**: Tasks affecting the same files must run sequentially
65 |    - **Validation checkpoints**: Verify each phase completion before proceeding
66 | 
67 | 6. Implementation execution rules:
68 |    - **Setup first**: Initialize project structure, dependencies, configuration
69 |    - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
70 |    - **Core development**: Implement models, services, CLI commands, endpoints
71 |    - **Integration work**: Database connections, middleware, logging, external services
72 |    - **Polish and validation**: Unit tests, performance optimization, documentation
73 | 
74 | 7. Progress tracking and error handling:
75 |    - Report progress after each completed task
76 |    - Halt execution if any non-parallel task fails
77 |    - For parallel tasks [P], continue with successful tasks, report failed ones
78 |    - Provide clear error messages with context for debugging
79 |    - Suggest next steps if implementation cannot proceed
80 |    - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
81 | 
82 | 8. Completion validation:
83 |    - Verify all required tasks are completed
84 |    - Check that implemented features match the original specification
85 |    - Validate that tests pass and coverage meets requirements
86 |    - Confirm the implementation follows the technical plan
87 |    - Report final status with summary of completed work
88 | 
89 | Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/tasks` first to regenerate the task list.
90 | 
```

--------------------------------------------------------------------------------
/.specify/scripts/bash/check-prerequisites.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/usr/bin/env bash
  2 | 
  3 | # Consolidated prerequisite checking script
  4 | #
  5 | # This script provides unified prerequisite checking for Spec-Driven Development workflow.
  6 | # It replaces the functionality previously spread across multiple scripts.
  7 | #
  8 | # Usage: ./check-prerequisites.sh [OPTIONS]
  9 | #
 10 | # OPTIONS:
 11 | #   --json              Output in JSON format
 12 | #   --require-tasks     Require tasks.md to exist (for implementation phase)
 13 | #   --include-tasks     Include tasks.md in AVAILABLE_DOCS list
 14 | #   --paths-only        Only output path variables (no validation)
 15 | #   --help, -h          Show help message
 16 | #
 17 | # OUTPUTS:
 18 | #   JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
 19 | #   Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
 20 | #   Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
 21 | 
 22 | set -e
 23 | 
 24 | # Parse command line arguments
 25 | JSON_MODE=false
 26 | REQUIRE_TASKS=false
 27 | INCLUDE_TASKS=false
 28 | PATHS_ONLY=false
 29 | 
 30 | for arg in "$@"; do
 31 |     case "$arg" in
 32 |         --json)
 33 |             JSON_MODE=true
 34 |             ;;
 35 |         --require-tasks)
 36 |             REQUIRE_TASKS=true
 37 |             ;;
 38 |         --include-tasks)
 39 |             INCLUDE_TASKS=true
 40 |             ;;
 41 |         --paths-only)
 42 |             PATHS_ONLY=true
 43 |             ;;
 44 |         --help|-h)
 45 |             cat << 'EOF'
 46 | Usage: check-prerequisites.sh [OPTIONS]
 47 | 
 48 | Consolidated prerequisite checking for Spec-Driven Development workflow.
 49 | 
 50 | OPTIONS:
 51 |   --json              Output in JSON format
 52 |   --require-tasks     Require tasks.md to exist (for implementation phase)
 53 |   --include-tasks     Include tasks.md in AVAILABLE_DOCS list
 54 |   --paths-only        Only output path variables (no prerequisite validation)
 55 |   --help, -h          Show this help message
 56 | 
 57 | EXAMPLES:
 58 |   # Check task prerequisites (plan.md required)
 59 |   ./check-prerequisites.sh --json
 60 |   
 61 |   # Check implementation prerequisites (plan.md + tasks.md required)
 62 |   ./check-prerequisites.sh --json --require-tasks --include-tasks
 63 |   
 64 |   # Get feature paths only (no validation)
 65 |   ./check-prerequisites.sh --paths-only
 66 |   
 67 | EOF
 68 |             exit 0
 69 |             ;;
 70 |         *)
 71 |             echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
 72 |             exit 1
 73 |             ;;
 74 |     esac
 75 | done
 76 | 
 77 | # Source common functions
 78 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 79 | source "$SCRIPT_DIR/common.sh"
 80 | 
 81 | # Get feature paths and validate branch
 82 | eval $(get_feature_paths)
 83 | check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
 84 | 
 85 | # If paths-only mode, output paths and exit (support JSON + paths-only combined)
 86 | if $PATHS_ONLY; then
 87 |     if $JSON_MODE; then
 88 |         # Minimal JSON paths payload (no validation performed)
 89 |         printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
 90 |             "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
 91 |     else
 92 |         echo "REPO_ROOT: $REPO_ROOT"
 93 |         echo "BRANCH: $CURRENT_BRANCH"
 94 |         echo "FEATURE_DIR: $FEATURE_DIR"
 95 |         echo "FEATURE_SPEC: $FEATURE_SPEC"
 96 |         echo "IMPL_PLAN: $IMPL_PLAN"
 97 |         echo "TASKS: $TASKS"
 98 |     fi
 99 |     exit 0
100 | fi
101 | 
102 | # Validate required directories and files
103 | if [[ ! -d "$FEATURE_DIR" ]]; then
104 |     echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
105 |     echo "Run /speckit.specify first to create the feature structure." >&2
106 |     exit 1
107 | fi
108 | 
109 | if [[ ! -f "$IMPL_PLAN" ]]; then
110 |     echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
111 |     echo "Run /speckit.plan first to create the implementation plan." >&2
112 |     exit 1
113 | fi
114 | 
115 | # Check for tasks.md if required
116 | if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
117 |     echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
118 |     echo "Run /speckit.tasks first to create the task list." >&2
119 |     exit 1
120 | fi
121 | 
122 | # Build list of available documents
123 | docs=()
124 | 
125 | # Always check these optional docs
126 | [[ -f "$RESEARCH" ]] && docs+=("research.md")
127 | [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
128 | 
129 | # Check contracts directory (only if it exists and has files)
130 | if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
131 |     docs+=("contracts/")
132 | fi
133 | 
134 | [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
135 | 
136 | # Include tasks.md if requested and it exists
137 | if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
138 |     docs+=("tasks.md")
139 | fi
140 | 
141 | # Output results
142 | if $JSON_MODE; then
143 |     # Build JSON array of documents
144 |     if [[ ${#docs[@]} -eq 0 ]]; then
145 |         json_docs="[]"
146 |     else
147 |         json_docs=$(printf '"%s",' "${docs[@]}")
148 |         json_docs="[${json_docs%,}]"
149 |     fi
150 |     
151 |     printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
152 | else
153 |     # Text output
154 |     echo "FEATURE_DIR:$FEATURE_DIR"
155 |     echo "AVAILABLE_DOCS:"
156 |     
157 |     # Show status of each potential document
158 |     check_file "$RESEARCH" "research.md"
159 |     check_file "$DATA_MODEL" "data-model.md"
160 |     check_dir "$CONTRACTS_DIR" "contracts/"
161 |     check_file "$QUICKSTART" "quickstart.md"
162 |     
163 |     if $INCLUDE_TASKS; then
164 |         check_file "$TASKS" "tasks.md"
165 |     fi
166 | fi
```

--------------------------------------------------------------------------------
/specs/001-i-want-to/test-dev-api.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | # Load environment variables from .env file
  4 | set -a
  5 | source "$(dirname "$0")/../../.env"
  6 | set +a
  7 | 
  8 | JIRA_HOST="${ATLASSIAN_HOST}"
  9 | JIRA_EMAIL="${ATLASSIAN_EMAIL}"
 10 | JIRA_TOKEN="${ATLASSIAN_TOKEN}"
 11 | ISSUE_KEY="SHTP-6050"
 12 | 
 13 | echo "========================================="
 14 | echo "Step 1: Getting numeric issue ID for ${ISSUE_KEY}"
 15 | echo "========================================="
 16 | 
 17 | ISSUE_RESPONSE=$(curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 18 |   -H "Accept: application/json" \
 19 |   "${JIRA_HOST}/rest/api/3/issue/${ISSUE_KEY}?fields=id")
 20 | 
 21 | echo "$ISSUE_RESPONSE" | jq '.'
 22 | 
 23 | ISSUE_ID=$(echo "$ISSUE_RESPONSE" | jq -r '.id')
 24 | 
 25 | if [ -z "$ISSUE_ID" ] || [ "$ISSUE_ID" = "null" ]; then
 26 |   echo "ERROR: Could not get issue ID"
 27 |   exit 1
 28 | fi
 29 | 
 30 | echo ""
 31 | echo "Issue ID: ${ISSUE_ID}"
 32 | echo ""
 33 | 
 34 | echo "========================================="
 35 | echo "Step 2: Getting development summary"
 36 | echo "========================================="
 37 | 
 38 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 39 |   -H "Accept: application/json" \
 40 |   "${JIRA_HOST}/rest/dev-status/latest/issue/summary?issueId=${ISSUE_ID}" | jq '.'
 41 | 
 42 | echo ""
 43 | echo "========================================="
 44 | echo "Step 3: Getting development details (repository) - GitLab"
 45 | echo "========================================="
 46 | 
 47 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 48 |   -H "Accept: application/json" \
 49 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=GitLab&dataType=repository" | jq '.'
 50 | 
 51 | echo ""
 52 | echo "========================================="
 53 | echo "Step 4: Getting development details (branch) - GitLab"
 54 | echo "========================================="
 55 | 
 56 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 57 |   -H "Accept: application/json" \
 58 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=GitLab&dataType=branch" | jq '.'
 59 | 
 60 | echo ""
 61 | echo "========================================="
 62 | echo "Step 5: Getting development details (pullrequest) - GitLab"
 63 | echo "========================================="
 64 | 
 65 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 66 |   -H "Accept: application/json" \
 67 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=GitLab&dataType=pullrequest" | jq '.'
 68 | 
 69 | echo ""
 70 | echo "========================================="
 71 | echo "Step 6: Getting development details (commit) - GitLab"
 72 | echo "========================================="
 73 | 
 74 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 75 |   -H "Accept: application/json" \
 76 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=GitLab&dataType=commit" | jq '.'
 77 | 
 78 | echo ""
 79 | echo "========================================="
 80 | echo "Step 7: Getting build information - All types"
 81 | echo "========================================="
 82 | 
 83 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 84 |   -H "Accept: application/json" \
 85 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&dataType=build" | jq '.'
 86 | 
 87 | echo ""
 88 | echo "========================================="
 89 | echo "Step 8: Getting build information - cloud-providers"
 90 | echo "========================================="
 91 | 
 92 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
 93 |   -H "Accept: application/json" \
 94 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=cloud-providers&dataType=build" | jq '.'
 95 | 
 96 | echo ""
 97 | echo "========================================="
 98 | echo "Step 9: Getting deployment information - All types"
 99 | echo "========================================="
100 | 
101 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
102 |   -H "Accept: application/json" \
103 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&dataType=deployment" | jq '.'
104 | 
105 | echo ""
106 | echo "========================================="
107 | echo "Step 10: Getting deployment-environment information"
108 | echo "========================================="
109 | 
110 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
111 |   -H "Accept: application/json" \
112 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&dataType=deployment-environment" | jq '.'
113 | 
114 | echo ""
115 | echo "========================================="
116 | echo "Step 11: Getting review information"
117 | echo "========================================="
118 | 
119 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
120 |   -H "Accept: application/json" \
121 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&dataType=review" | jq '.'
122 | 
123 | echo ""
124 | echo "========================================="
125 | echo "Step 12: Getting ALL development details - GitLab"
126 | echo "========================================="
127 | 
128 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
129 |   -H "Accept: application/json" \
130 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}&applicationType=GitLab" | jq '.'
131 | 
132 | echo ""
133 | echo "========================================="
134 | echo "Step 13: Getting ALL development details - No filters"
135 | echo "========================================="
136 | 
137 | curl -s -u "${JIRA_EMAIL}:${JIRA_TOKEN}" \
138 |   -H "Accept: application/json" \
139 |   "${JIRA_HOST}/rest/dev-status/latest/issue/detail?issueId=${ISSUE_ID}" | jq '.'
140 | 
```

--------------------------------------------------------------------------------
/.claude/commands/speckit.constitution.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync.
 3 | ---
 4 | 
 5 | ## User Input
 6 | 
 7 | ```text
 8 | $ARGUMENTS
 9 | ```
10 | 
11 | You **MUST** consider the user input before proceeding (if not empty).
12 | 
13 | ## Outline
14 | 
15 | You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
16 | 
17 | Follow this execution flow:
18 | 
19 | 1. Load the existing constitution template at `.specify/memory/constitution.md`.
20 |    - Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
21 |    **IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
22 | 
23 | 2. Collect/derive values for placeholders:
24 |    - If user input (conversation) supplies a value, use it.
25 |    - Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
26 |    - For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
27 |    - `CONSTITUTION_VERSION` must increment according to semantic versioning rules:
28 |      * MAJOR: Backward incompatible governance/principle removals or redefinitions.
29 |      * MINOR: New principle/section added or materially expanded guidance.
30 |      * PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
31 |    - If version bump type ambiguous, propose reasoning before finalizing.
32 | 
33 | 3. Draft the updated constitution content:
34 |    - Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
35 |    - Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
36 |    - Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
37 |    - Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
38 | 
39 | 4. Consistency propagation checklist (convert prior checklist into active validations):
40 |    - Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
41 |    - Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
42 |    - Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
43 |    - Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required.
44 |    - Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
45 | 
46 | 5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
47 |    - Version change: old → new
48 |    - List of modified principles (old title → new title if renamed)
49 |    - Added sections
50 |    - Removed sections
51 |    - Templates requiring updates (✅ updated / ⚠ pending) with file paths
52 |    - Follow-up TODOs if any placeholders intentionally deferred.
53 | 
54 | 6. Validation before final output:
55 |    - No remaining unexplained bracket tokens.
56 |    - Version line matches report.
57 |    - Dates ISO format YYYY-MM-DD.
58 |    - Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate).
59 | 
60 | 7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite).
61 | 
62 | 8. Output a final summary to the user with:
63 |    - New version and bump rationale.
64 |    - Any files flagged for manual follow-up.
65 |    - Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`).
66 | 
67 | Formatting & Style Requirements:
68 | - Use Markdown headings exactly as in the template (do not demote/promote levels).
69 | - Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks.
70 | - Keep a single blank line between sections.
71 | - Avoid trailing whitespace.
72 | 
73 | If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps.
74 | 
75 | If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
76 | 
77 | Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
78 | 
```

--------------------------------------------------------------------------------
/tools/jira_relationship.go:
--------------------------------------------------------------------------------

```go
  1 | package tools
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"fmt"
  6 | 	"strings"
  7 | 
  8 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
  9 | 	"github.com/mark3labs/mcp-go/mcp"
 10 | 	"github.com/mark3labs/mcp-go/server"
 11 | 	"github.com/nguyenvanduocit/jira-mcp/services"
 12 | )
 13 | 
 14 | // Input types for typed tools
 15 | type GetRelatedIssuesInput struct {
 16 | 	IssueKey string `json:"issue_key" validate:"required"`
 17 | }
 18 | 
 19 | type LinkIssuesInput struct {
 20 | 	InwardIssue  string `json:"inward_issue" validate:"required"`
 21 | 	OutwardIssue string `json:"outward_issue" validate:"required"`
 22 | 	LinkType     string `json:"link_type" validate:"required"`
 23 | 	Comment      string `json:"comment,omitempty"`
 24 | }
 25 | 
 26 | func RegisterJiraRelationshipTool(s *server.MCPServer) {
 27 | 	jiraRelationshipTool := mcp.NewTool("jira_get_related_issues",
 28 | 		mcp.WithDescription("Retrieve issues that have a relationship with a given issue, such as blocks, is blocked by, relates to, etc."),
 29 | 		mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")),
 30 | 	)
 31 | 	s.AddTool(jiraRelationshipTool, mcp.NewTypedToolHandler(jiraRelationshipHandler))
 32 | 
 33 | 	jiraLinkTool := mcp.NewTool("jira_link_issues",
 34 | 		mcp.WithDescription("Create a link between two Jira issues, defining their relationship (e.g., blocks, duplicates, relates to)"),
 35 | 		mcp.WithString("inward_issue", mcp.Required(), mcp.Description("The key of the inward issue (e.g., KP-1, PROJ-123)")),
 36 | 		mcp.WithString("outward_issue", mcp.Required(), mcp.Description("The key of the outward issue (e.g., KP-2, PROJ-123)")),
 37 | 		mcp.WithString("link_type", mcp.Required(), mcp.Description("The type of link between issues (e.g., Duplicate, Blocks, Relates)")),
 38 | 		mcp.WithString("comment", mcp.Description("Optional comment to add when creating the link")),
 39 | 	)
 40 | 	s.AddTool(jiraLinkTool, mcp.NewTypedToolHandler(jiraLinkHandler))
 41 | }
 42 | 
 43 | func jiraRelationshipHandler(ctx context.Context, request mcp.CallToolRequest, input GetRelatedIssuesInput) (*mcp.CallToolResult, error) {
 44 | 	client := services.JiraClient()
 45 | 	
 46 | 	// Get the issue with the 'issuelinks' field
 47 | 	issue, response, err := client.Issue.Get(ctx, input.IssueKey, nil, []string{"issuelinks"})
 48 | 	if err != nil {
 49 | 		if response != nil {
 50 | 			return nil, fmt.Errorf("failed to get issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
 51 | 		}
 52 | 		return nil, fmt.Errorf("failed to get issue: %v", err)
 53 | 	}
 54 | 
 55 | 	if issue.Fields.IssueLinks == nil || len(issue.Fields.IssueLinks) == 0 {
 56 | 		return mcp.NewToolResultText(fmt.Sprintf("Issue %s has no linked issues.", input.IssueKey)), nil
 57 | 	}
 58 | 
 59 | 	var sb strings.Builder
 60 | 	sb.WriteString(fmt.Sprintf("Related issues for %s:\n\n", input.IssueKey))
 61 | 
 62 | 	for _, link := range issue.Fields.IssueLinks {
 63 | 		// Determine the relationship type and related issue
 64 | 		var relatedIssue string
 65 | 		var relationshipType string
 66 | 		var direction string
 67 | 
 68 | 		if link.InwardIssue != nil {
 69 | 			relatedIssue = link.InwardIssue.Key
 70 | 			relationshipType = link.Type.Inward
 71 | 			direction = "inward"
 72 | 		} else if link.OutwardIssue != nil {
 73 | 			relatedIssue = link.OutwardIssue.Key
 74 | 			relationshipType = link.Type.Outward
 75 | 			direction = "outward"
 76 | 		} else {
 77 | 			continue // Skip if no related issue
 78 | 		}
 79 | 
 80 | 		var summary string
 81 | 		if direction == "inward" && link.InwardIssue.Fields.Summary != "" {
 82 | 			summary = link.InwardIssue.Fields.Summary
 83 | 		} else if direction == "outward" && link.OutwardIssue.Fields.Summary != "" {
 84 | 			summary = link.OutwardIssue.Fields.Summary
 85 | 		}
 86 | 
 87 | 		var status string
 88 | 		if direction == "inward" && link.InwardIssue.Fields.Status != nil {
 89 | 			status = link.InwardIssue.Fields.Status.Name
 90 | 		} else if direction == "outward" && link.OutwardIssue.Fields.Status != nil {
 91 | 			status = link.OutwardIssue.Fields.Status.Name
 92 | 		} else {
 93 | 			status = "Unknown"
 94 | 		}
 95 | 
 96 | 		sb.WriteString(fmt.Sprintf("Relationship: %s\n", relationshipType))
 97 | 		sb.WriteString(fmt.Sprintf("Issue: %s\n", relatedIssue))
 98 | 		sb.WriteString(fmt.Sprintf("Summary: %s\n", summary))
 99 | 		sb.WriteString(fmt.Sprintf("Status: %s\n", status))
100 | 		sb.WriteString("\n")
101 | 	}
102 | 
103 | 	return mcp.NewToolResultText(sb.String()), nil
104 | } 
105 | 
106 | 
107 | func jiraLinkHandler(ctx context.Context, request mcp.CallToolRequest, input LinkIssuesInput) (*mcp.CallToolResult, error) {
108 | 	client := services.JiraClient()
109 | 
110 | 	// Create the link payload
111 | 	payload := &models.LinkPayloadSchemeV3{
112 | 		InwardIssue: &models.LinkedIssueScheme{
113 | 			Key: input.InwardIssue,
114 | 		},
115 | 		OutwardIssue: &models.LinkedIssueScheme{
116 | 			Key: input.OutwardIssue,
117 | 		},
118 | 		Type: &models.LinkTypeScheme{
119 | 			Name: input.LinkType,
120 | 		},
121 | 	}
122 | 
123 | 	// Add comment if provided
124 | 	if input.Comment != "" {
125 | 		payload.Comment = &models.CommentPayloadScheme{
126 | 			Body: &models.CommentNodeScheme{
127 | 				Type: "text",
128 | 				Text: input.Comment,
129 | 			},
130 | 		}
131 | 	}
132 | 
133 | 	// Create the link
134 | 	response, err := client.Issue.Link.Create(ctx, payload)
135 | 	if err != nil {
136 | 		if response != nil {
137 | 			return nil, fmt.Errorf("failed to link issues: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint)
138 | 		}
139 | 		return nil, fmt.Errorf("failed to link issues: %v", err)
140 | 	}
141 | 
142 | 	return mcp.NewToolResultText(fmt.Sprintf("Successfully linked issues %s and %s with link type \"%s\"", input.InwardIssue, input.OutwardIssue, input.LinkType)), nil
143 | } 
```

--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------

```go
  1 | package main
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"errors"
  6 | 	"flag"
  7 | 	"fmt"
  8 | 	"log"
  9 | 	"os"
 10 | 	"strings"
 11 | 
 12 | 	"github.com/joho/godotenv"
 13 | 	"github.com/mark3labs/mcp-go/server"
 14 | 	"github.com/nguyenvanduocit/jira-mcp/prompts"
 15 | 	"github.com/nguyenvanduocit/jira-mcp/tools"
 16 | )
 17 | 
 18 | func main() {
 19 | 	envFile := flag.String("env", "", "Path to environment file (optional when environment variables are set directly)")
 20 | 	httpPort := flag.String("http_port", "", "Port for HTTP server. If not provided, will use stdio")
 21 | 	flag.Parse()
 22 | 
 23 | 	// Load environment file if specified
 24 | 	if *envFile != "" {
 25 | 		if err := godotenv.Load(*envFile); err != nil {
 26 | 			fmt.Printf("⚠️  Warning: Error loading env file %s: %v\n", *envFile, err)
 27 | 		} else {
 28 | 			fmt.Printf("✅ Loaded environment variables from %s\n", *envFile)
 29 | 		}
 30 | 	}
 31 | 
 32 | 	// Check required environment variables
 33 | 	requiredEnvs := []string{"ATLASSIAN_HOST", "ATLASSIAN_EMAIL", "ATLASSIAN_TOKEN"}
 34 | 	missingEnvs := []string{}
 35 | 	for _, env := range requiredEnvs {
 36 | 		if os.Getenv(env) == "" {
 37 | 			missingEnvs = append(missingEnvs, env)
 38 | 		}
 39 | 	}
 40 | 
 41 | 	if len(missingEnvs) > 0 {
 42 | 		fmt.Println("❌ Configuration Error: Missing required environment variables")
 43 | 		fmt.Println()
 44 | 		fmt.Println("Missing variables:")
 45 | 		for _, env := range missingEnvs {
 46 | 			fmt.Printf("  - %s\n", env)
 47 | 		}
 48 | 		fmt.Println()
 49 | 		fmt.Println("📋 Setup Instructions:")
 50 | 		fmt.Println("1. Get your Atlassian API token from: https://id.atlassian.com/manage-profile/security/api-tokens")
 51 | 		fmt.Println("2. Set the environment variables:")
 52 | 		fmt.Println()
 53 | 		fmt.Println("   Option A - Using .env file:")
 54 | 		fmt.Println("   Create a .env file with:")
 55 |                fmt.Println("   ATLASSIAN_HOST=https://your-domain.atlassian.net")
 56 | 		fmt.Println("   [email protected]")
 57 | 		fmt.Println("   ATLASSIAN_TOKEN=your-api-token")
 58 | 		fmt.Println()
 59 | 		fmt.Println("   Option B - Using environment variables:")
 60 |                fmt.Println("   export ATLASSIAN_HOST=https://your-domain.atlassian.net")
 61 | 		fmt.Println("   export [email protected]")
 62 | 		fmt.Println("   export ATLASSIAN_TOKEN=your-api-token")
 63 | 		fmt.Println()
 64 | 		fmt.Println("   Option C - Using Docker:")
 65 |                fmt.Printf("   docker run -e ATLASSIAN_HOST=https://your-domain.atlassian.net \\\n")
 66 | 		fmt.Printf("              -e [email protected] \\\n")
 67 | 		fmt.Printf("              -e ATLASSIAN_TOKEN=your-api-token \\\n")
 68 | 		fmt.Printf("              ghcr.io/nguyenvanduocit/jira-mcp:latest\n")
 69 | 		fmt.Println()
 70 | 		os.Exit(1)
 71 | 	}
 72 | 
 73 | 	fmt.Println("✅ All required environment variables are set")
 74 | 	fmt.Printf("🔗 Connected to: %s\n", os.Getenv("ATLASSIAN_HOST"))
 75 | 
 76 | 	mcpServer := server.NewMCPServer(
 77 | 		"Jira MCP",
 78 | 		"1.0.1",
 79 | 		server.WithLogging(),
 80 | 		server.WithPromptCapabilities(true),
 81 | 		server.WithResourceCapabilities(true, true),
 82 | 		server.WithRecovery(),
 83 | 	)
 84 | 
 85 | 	// Register all Jira tools
 86 | 	tools.RegisterJiraIssueTool(mcpServer)
 87 | 	tools.RegisterJiraSearchTool(mcpServer)
 88 | 	tools.RegisterJiraSprintTool(mcpServer)
 89 | 	tools.RegisterJiraStatusTool(mcpServer)
 90 | 	tools.RegisterJiraTransitionTool(mcpServer)
 91 | 	tools.RegisterJiraWorklogTool(mcpServer)
 92 | 	tools.RegisterJiraCommentTools(mcpServer)
 93 | 	tools.RegisterJiraHistoryTool(mcpServer)
 94 | 	tools.RegisterJiraRelationshipTool(mcpServer)
 95 | 	tools.RegisterJiraVersionTool(mcpServer)
 96 | 	tools.RegisterJiraDevelopmentTool(mcpServer)
 97 | 
 98 | 	// Register all Jira prompts
 99 | 	prompts.RegisterJiraPrompts(mcpServer)
100 | 
101 | 	if *httpPort != "" {
102 | 		fmt.Println()
103 | 		fmt.Println("🚀 Starting Jira MCP Server in HTTP mode...")
104 | 		fmt.Printf("📡 Server will be available at: http://localhost:%s/mcp\n", *httpPort)
105 | 		fmt.Println()
106 | 		fmt.Println("📋 Cursor Configuration:")
107 | 		fmt.Println("Add the following to your Cursor MCP settings (.cursor/mcp.json):")
108 | 		fmt.Println()
109 | 		fmt.Println("```json")
110 | 		fmt.Println("{")
111 | 		fmt.Println("  \"mcpServers\": {")
112 | 		fmt.Println("    \"jira\": {")
113 | 		fmt.Printf("      \"url\": \"http://localhost:%s/mcp\"\n", *httpPort)
114 | 		fmt.Println("    }")
115 | 		fmt.Println("  }")
116 | 		fmt.Println("}")
117 | 		fmt.Println("```")
118 | 		fmt.Println()
119 | 		fmt.Println("💡 Tips:")
120 | 		fmt.Println("- Restart Cursor after adding the configuration")
121 | 		fmt.Println("- Test the connection by asking Claude: 'List my Jira projects'")
122 | 		fmt.Println("- Use '@jira' in Cursor to reference Jira-related context")
123 | 		fmt.Println()
124 | 		fmt.Println("🔄 Server starting...")
125 | 		
126 | 		httpServer := server.NewStreamableHTTPServer(mcpServer, server.WithEndpointPath("/mcp"))
127 | 		if err := httpServer.Start(fmt.Sprintf(":%s", *httpPort)); err != nil && !isContextCanceled(err) {
128 | 			log.Fatalf("❌ Server error: %v", err)
129 | 		}
130 | 	} else {
131 | 		if err := server.ServeStdio(mcpServer); err != nil && !isContextCanceled(err) {
132 | 			log.Fatalf("❌ Server error: %v", err)
133 | 		}
134 | 	}
135 | }
136 | 
137 | // IsContextCanceled checks if the error is related to context cancellation
138 | func isContextCanceled(err error) bool {
139 | 	if err == nil {
140 | 		return false
141 | 	}
142 | 	
143 | 	// Check if it's directly context.Canceled
144 | 	if errors.Is(err, context.Canceled) {
145 | 		return true
146 | 	}
147 | 	
148 | 	// Check if the error message contains context canceled
149 | 	errMsg := strings.ToLower(err.Error())
150 | 	return strings.Contains(errMsg, "context canceled") || 
151 | 	       strings.Contains(errMsg, "operation was canceled") ||
152 | 	       strings.Contains(errMsg, "context deadline exceeded")
153 | }
154 | 
```

--------------------------------------------------------------------------------
/.claude/commands/speckit.tasks.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
  3 | ---
  4 | 
  5 | ## User Input
  6 | 
  7 | ```text
  8 | $ARGUMENTS
  9 | ```
 10 | 
 11 | You **MUST** consider the user input before proceeding (if not empty).
 12 | 
 13 | ## Outline
 14 | 
 15 | 1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
 16 | 
 17 | 2. **Load design documents**: Read from FEATURE_DIR:
 18 |    - **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
 19 |    - **Optional**: data-model.md (entities), contracts/ (API endpoints), research.md (decisions), quickstart.md (test scenarios)
 20 |    - Note: Not all projects have all documents. Generate tasks based on what's available.
 21 | 
 22 | 3. **Execute task generation workflow** (follow the template structure):
 23 |    - Load plan.md and extract tech stack, libraries, project structure
 24 |    - **Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)**
 25 |    - If data-model.md exists: Extract entities → map to user stories
 26 |    - If contracts/ exists: Each file → map endpoints to user stories
 27 |    - If research.md exists: Extract decisions → generate setup tasks
 28 |    - **Generate tasks ORGANIZED BY USER STORY**:
 29 |      - Setup tasks (shared infrastructure needed by all stories)
 30 |      - **Foundational tasks (prerequisites that must complete before ANY user story can start)**
 31 |      - For each user story (in priority order P1, P2, P3...):
 32 |        - Group all tasks needed to complete JUST that story
 33 |        - Include models, services, endpoints, UI components specific to that story
 34 |        - Mark which tasks are [P] parallelizable
 35 |        - If tests requested: Include tests specific to that story
 36 |      - Polish/Integration tasks (cross-cutting concerns)
 37 |    - **Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature spec or user asks for TDD approach
 38 |    - Apply task rules:
 39 |      - Different files = mark [P] for parallel
 40 |      - Same file = sequential (no [P])
 41 |      - If tests requested: Tests before implementation (TDD order)
 42 |    - Number tasks sequentially (T001, T002...)
 43 |    - Generate dependency graph showing user story completion order
 44 |    - Create parallel execution examples per user story
 45 |    - Validate task completeness (each user story has all needed tasks, independently testable)
 46 | 
 47 | 4. **Generate tasks.md**: Use `.specify.specify/templates/tasks-template.md` as structure, fill with:
 48 |    - Correct feature name from plan.md
 49 |    - Phase 1: Setup tasks (project initialization)
 50 |    - Phase 2: Foundational tasks (blocking prerequisites for all user stories)
 51 |    - Phase 3+: One phase per user story (in priority order from spec.md)
 52 |      - Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
 53 |      - Clear [Story] labels (US1, US2, US3...) for each task
 54 |      - [P] markers for parallelizable tasks within each story
 55 |      - Checkpoint markers after each story phase
 56 |    - Final Phase: Polish & cross-cutting concerns
 57 |    - Numbered tasks (T001, T002...) in execution order
 58 |    - Clear file paths for each task
 59 |    - Dependencies section showing story completion order
 60 |    - Parallel execution examples per story
 61 |    - Implementation strategy section (MVP first, incremental delivery)
 62 | 
 63 | 5. **Report**: Output path to generated tasks.md and summary:
 64 |    - Total task count
 65 |    - Task count per user story
 66 |    - Parallel opportunities identified
 67 |    - Independent test criteria for each story
 68 |    - Suggested MVP scope (typically just User Story 1)
 69 | 
 70 | Context for task generation: $ARGUMENTS
 71 | 
 72 | The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
 73 | 
 74 | ## Task Generation Rules
 75 | 
 76 | **IMPORTANT**: Tests are optional. Only generate test tasks if the user explicitly requested testing or TDD approach in the feature specification.
 77 | 
 78 | **CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
 79 | 
 80 | 1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
 81 |    - Each user story (P1, P2, P3...) gets its own phase
 82 |    - Map all related components to their story:
 83 |      - Models needed for that story
 84 |      - Services needed for that story
 85 |      - Endpoints/UI needed for that story
 86 |      - If tests requested: Tests specific to that story
 87 |    - Mark story dependencies (most stories should be independent)
 88 |    
 89 | 2. **From Contracts**:
 90 |    - Map each contract/endpoint → to the user story it serves
 91 |    - If tests requested: Each contract → contract test task [P] before implementation in that story's phase
 92 |    
 93 | 3. **From Data Model**:
 94 |    - Map each entity → to the user story(ies) that need it
 95 |    - If entity serves multiple stories: Put in earliest story or Setup phase
 96 |    - Relationships → service layer tasks in appropriate story phase
 97 |    
 98 | 4. **From Setup/Infrastructure**:
 99 |    - Shared infrastructure → Setup phase (Phase 1)
100 |    - Foundational/blocking tasks → Foundational phase (Phase 2)
101 |      - Examples: Database schema setup, authentication framework, core libraries, base configurations
102 |      - These MUST complete before any user story can be implemented
103 |    - Story-specific setup → within that story's phase
104 | 
105 | 5. **Ordering**:
106 |    - Phase 1: Setup (project initialization)
107 |    - Phase 2: Foundational (blocking prerequisites - must complete before user stories)
108 |    - Phase 3+: User Stories in priority order (P1, P2, P3...)
109 |      - Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
110 |    - Final Phase: Polish & Cross-Cutting Concerns
111 |    - Each user story phase should be a complete, independently testable increment
112 | 
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Changelog
  2 | 
  3 | ## [1.17.1](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.17.0...v1.17.1) (2025-10-09)
  4 | 
  5 | 
  6 | ### Bug Fixes
  7 | 
  8 | * disable provenance and SBOM to prevent unknown/unknown platform entry ([2acf42f](https://github.com/nguyenvanduocit/jira-mcp/commit/2acf42f1c1fbb43f078c2c4828bf0fd23493c026))
  9 | 
 10 | ## [1.17.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.16.0...v1.17.0) (2025-10-09)
 11 | 
 12 | 
 13 | ### Features
 14 | 
 15 | * add ARM64 support for Docker images ([d535e52](https://github.com/nguyenvanduocit/jira-mcp/commit/d535e52a3c966ca2af3f67d386282ed640e7b387))
 16 | 
 17 | ## [1.16.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.15.0...v1.16.0) (2025-10-07)
 18 | 
 19 | 
 20 | ### Features
 21 | 
 22 | * add build information support and prompts functionality ([d3df256](https://github.com/nguyenvanduocit/jira-mcp/commit/d3df256f80a181e80eca0510583e9771c47b9a58))
 23 | 
 24 | ## [1.15.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.14.0...v1.15.0) (2025-10-07)
 25 | 
 26 | 
 27 | ### Features
 28 | 
 29 | * add development information tool for Jira issues ([b0c2ec8](https://github.com/nguyenvanduocit/jira-mcp/commit/b0c2ec84de8d71be7ddb7cdaf976c31ea803ef8b))
 30 | 
 31 | ## [1.14.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.13.0...v1.14.0) (2025-09-23)
 32 | 
 33 | 
 34 | ### Features
 35 | 
 36 | * add jira_ prefix to all tool names for better LLM discoverability ([f44cd03](https://github.com/nguyenvanduocit/jira-mcp/commit/f44cd03a7c467255192f65404cfc15cc53ae0b76))
 37 | 
 38 | ## [1.13.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.12.1...v1.13.0) (2025-09-23)
 39 | 
 40 | 
 41 | ### Features
 42 | 
 43 | * **docs:** rewrite README with clear USP and 2‑minute quick start ([69b7b20](https://github.com/nguyenvanduocit/jira-mcp/commit/69b7b20166c424efe485fa2403193105012f2973))
 44 | 
 45 | ## [1.12.1](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.12.0...v1.12.1) (2025-09-23)
 46 | 
 47 | 
 48 | ### Bug Fixes
 49 | 
 50 | * migrate JQL search to new API endpoint ([bf6b6a2](https://github.com/nguyenvanduocit/jira-mcp/commit/bf6b6a2a105c5e34a0cbc6c3495f711d70cd47aa))
 51 | 
 52 | ## [1.12.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.11.0...v1.12.0) (2025-09-08)
 53 | 
 54 | 
 55 | ### Features
 56 | 
 57 | * upgrade to Jira API v3 ([1b65607](https://github.com/nguyenvanduocit/jira-mcp/commit/1b6560750ac0f4d37b5cc2fdf004d15932b4346b))
 58 | 
 59 | ## [1.11.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.10.0...v1.11.0) (2025-06-11)
 60 | 
 61 | 
 62 | ### Features
 63 | 
 64 | * support streamable http ([a67657a](https://github.com/nguyenvanduocit/jira-mcp/commit/a67657a9649b53ec02a5b2a4eb0789810bc7b372))
 65 | 
 66 | ## [1.10.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.9.0...v1.10.0) (2025-06-11)
 67 | 
 68 | 
 69 | ### Features
 70 | 
 71 | * update foỏmat of sprint ([d378f75](https://github.com/nguyenvanduocit/jira-mcp/commit/d378f7510fb21b8f4fd3ee677d3a1cf54c2ad025))
 72 | 
 73 | ## [1.9.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.8.0...v1.9.0) (2025-06-06)
 74 | 
 75 | 
 76 | ### Features
 77 | 
 78 | * add get_active_sprint tool ([3095319](https://github.com/nguyenvanduocit/jira-mcp/commit/30953192ce5e88d4940fea09f7a2331ae2516b54))
 79 | * **sprint:** add get_active_sprint tool ([89724b2](https://github.com/nguyenvanduocit/jira-mcp/commit/89724b26b7e9c19dfc16d1c8d81455801145fb27))
 80 | * **sprint:** enhance list_sprints and get_active_sprint tools with project_key support ([f31cac4](https://github.com/nguyenvanduocit/jira-mcp/commit/f31cac4701ef0e88e2c2b73cc950770c6b5cda02))
 81 | 
 82 | ## [1.8.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.7.0...v1.8.0) (2025-06-05)
 83 | 
 84 | 
 85 | ### Features
 86 | 
 87 | * **tools:** enhance issue tools with flexible field selection ([40fef1d](https://github.com/nguyenvanduocit/jira-mcp/commit/40fef1dcaa4ef0ed7833cd6db24e3e31c0a35f73))
 88 | 
 89 | ## [1.7.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.6.0...v1.7.0) (2025-06-05)
 90 | 
 91 | 
 92 | ### Features
 93 | 
 94 | * **jira:** enhance issue retrieval with changelog and story point estimate ([6e9902c](https://github.com/nguyenvanduocit/jira-mcp/commit/6e9902c464430ffc759124792fe0907697d80fab))
 95 | 
 96 | 
 97 | ### Bug Fixes
 98 | 
 99 | * retrieve all issue comments ([a7cfae5](https://github.com/nguyenvanduocit/jira-mcp/commit/a7cfae5e459fd50b3a62f80223bddfc659a5453b))
100 | 
101 | ## [1.6.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.5.0...v1.6.0) (2025-05-06)
102 | 
103 | 
104 | ### Features
105 | 
106 | * bump version ([cd2a85c](https://github.com/nguyenvanduocit/jira-mcp/commit/cd2a85c42c8594240e8718524ac0082acf1b7db7))
107 | 
108 | ## [1.5.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.4.0...v1.5.0) (2025-04-18)
109 | 
110 | 
111 | ### Features
112 | 
113 | * **docker:** add GitHub Container Registry support ([f939b62](https://github.com/nguyenvanduocit/jira-mcp/commit/f939b629e764d4fe470f6954cc0d281eccde913f))
114 | * **sprint:** add get_sprint tool to retrieve details of a specific sprint ([6756a1c](https://github.com/nguyenvanduocit/jira-mcp/commit/6756a1c79ed0692aeac9d12287fd92ef6bc5f1c2))
115 | 
116 | ## [1.4.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.3.0...v1.4.0) (2025-04-18)
117 | 
118 | 
119 | ### Features
120 | 
121 | * **tools:** simplify tool naming and add issue relationship features ([02279ea](https://github.com/nguyenvanduocit/jira-mcp/commit/02279ead729b7a9bd6e78d6ed7903931d39c1580))
122 | 
123 | ## [1.3.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.2.0...v1.3.0) (2025-04-17)
124 | 
125 | 
126 | ### Features
127 | 
128 | * **jira:** add issue history and relationship tools ([fc44bd4](https://github.com/nguyenvanduocit/jira-mcp/commit/fc44bd4384775260bf8ea7a0373c89d7053b6450))
129 | 
130 | ## [1.2.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.1.0...v1.2.0) (2025-04-15)
131 | 
132 | 
133 | ### Features
134 | 
135 | * **docker:** add Docker support with build and run instructions ([979cd45](https://github.com/nguyenvanduocit/jira-mcp/commit/979cd459663c0004c566cda658efbf9fca50bf52))
136 | * **docker:** add Docker support with build and run instructions ([078062e](https://github.com/nguyenvanduocit/jira-mcp/commit/078062ed7ba2686483a9df4c6000462d5b4fed3a))
137 | 
138 | ## [1.1.0](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.0.1...v1.1.0) (2025-04-04)
139 | 
140 | 
141 | ### Features
142 | 
143 | * **jira:** add worklog functionality ([82ea857](https://github.com/nguyenvanduocit/jira-mcp/commit/82ea85767653ea5de4f20beb6585d9f694696a9a))
144 | 
145 | ## [1.0.1](https://github.com/nguyenvanduocit/jira-mcp/compare/v1.0.0...v1.0.1) (2025-04-04)
146 | 
147 | 
148 | ### Bug Fixes
149 | 
150 | * **api:** update Jira comment API implementation ([12798ee](https://github.com/nguyenvanduocit/jira-mcp/commit/12798ee285f0b8d5c70689db87fa60b74e72376d))
151 | 
152 | ## 1.0.0 (2025-03-25)
153 | 
154 | 
155 | ### Features
156 | 
157 | * add gitleaks workflow for secret scanning ([9bb46a0](https://github.com/nguyenvanduocit/jira-mcp/commit/9bb46a0f63793934470a1701a42d2413f29898f8))
158 | * init ([6e960c0](https://github.com/nguyenvanduocit/jira-mcp/commit/6e960c048f69fe61baee42c3061aef0a44602be3))
159 | 
```

--------------------------------------------------------------------------------
/.claude/commands/speckit.analyze.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation.
  3 | ---
  4 | 
  5 | ## User Input
  6 | 
  7 | ```text
  8 | $ARGUMENTS
  9 | ```
 10 | 
 11 | You **MUST** consider the user input before proceeding (if not empty).
 12 | 
 13 | ## Goal
 14 | 
 15 | Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/tasks` has successfully produced a complete `tasks.md`.
 16 | 
 17 | ## Operating Constraints
 18 | 
 19 | **STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
 20 | 
 21 | **Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/analyze`.
 22 | 
 23 | ## Execution Steps
 24 | 
 25 | ### 1. Initialize Analysis Context
 26 | 
 27 | Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
 28 | 
 29 | - SPEC = FEATURE_DIR/spec.md
 30 | - PLAN = FEATURE_DIR/plan.md
 31 | - TASKS = FEATURE_DIR/tasks.md
 32 | 
 33 | Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
 34 | 
 35 | ### 2. Load Artifacts (Progressive Disclosure)
 36 | 
 37 | Load only the minimal necessary context from each artifact:
 38 | 
 39 | **From spec.md:**
 40 | 
 41 | - Overview/Context
 42 | - Functional Requirements
 43 | - Non-Functional Requirements
 44 | - User Stories
 45 | - Edge Cases (if present)
 46 | 
 47 | **From plan.md:**
 48 | 
 49 | - Architecture/stack choices
 50 | - Data Model references
 51 | - Phases
 52 | - Technical constraints
 53 | 
 54 | **From tasks.md:**
 55 | 
 56 | - Task IDs
 57 | - Descriptions
 58 | - Phase grouping
 59 | - Parallel markers [P]
 60 | - Referenced file paths
 61 | 
 62 | **From constitution:**
 63 | 
 64 | - Load `.specify/memory/constitution.md` for principle validation
 65 | 
 66 | ### 3. Build Semantic Models
 67 | 
 68 | Create internal representations (do not include raw artifacts in output):
 69 | 
 70 | - **Requirements inventory**: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" → `user-can-upload-file`)
 71 | - **User story/action inventory**: Discrete user actions with acceptance criteria
 72 | - **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases)
 73 | - **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
 74 | 
 75 | ### 4. Detection Passes (Token-Efficient Analysis)
 76 | 
 77 | Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary.
 78 | 
 79 | #### A. Duplication Detection
 80 | 
 81 | - Identify near-duplicate requirements
 82 | - Mark lower-quality phrasing for consolidation
 83 | 
 84 | #### B. Ambiguity Detection
 85 | 
 86 | - Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria
 87 | - Flag unresolved placeholders (TODO, TKTK, ???, `<placeholder>`, etc.)
 88 | 
 89 | #### C. Underspecification
 90 | 
 91 | - Requirements with verbs but missing object or measurable outcome
 92 | - User stories missing acceptance criteria alignment
 93 | - Tasks referencing files or components not defined in spec/plan
 94 | 
 95 | #### D. Constitution Alignment
 96 | 
 97 | - Any requirement or plan element conflicting with a MUST principle
 98 | - Missing mandated sections or quality gates from constitution
 99 | 
100 | #### E. Coverage Gaps
101 | 
102 | - Requirements with zero associated tasks
103 | - Tasks with no mapped requirement/story
104 | - Non-functional requirements not reflected in tasks (e.g., performance, security)
105 | 
106 | #### F. Inconsistency
107 | 
108 | - Terminology drift (same concept named differently across files)
109 | - Data entities referenced in plan but absent in spec (or vice versa)
110 | - Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note)
111 | - Conflicting requirements (e.g., one requires Next.js while other specifies Vue)
112 | 
113 | ### 5. Severity Assignment
114 | 
115 | Use this heuristic to prioritize findings:
116 | 
117 | - **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality
118 | - **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion
119 | - **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case
120 | - **LOW**: Style/wording improvements, minor redundancy not affecting execution order
121 | 
122 | ### 6. Produce Compact Analysis Report
123 | 
124 | Output a Markdown report (no file writes) with the following structure:
125 | 
126 | ## Specification Analysis Report
127 | 
128 | | ID | Category | Severity | Location(s) | Summary | Recommendation |
129 | |----|----------|----------|-------------|---------|----------------|
130 | | A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
131 | 
132 | (Add one row per finding; generate stable IDs prefixed by category initial.)
133 | 
134 | **Coverage Summary Table:**
135 | 
136 | | Requirement Key | Has Task? | Task IDs | Notes |
137 | |-----------------|-----------|----------|-------|
138 | 
139 | **Constitution Alignment Issues:** (if any)
140 | 
141 | **Unmapped Tasks:** (if any)
142 | 
143 | **Metrics:**
144 | 
145 | - Total Requirements
146 | - Total Tasks
147 | - Coverage % (requirements with >=1 task)
148 | - Ambiguity Count
149 | - Duplication Count
150 | - Critical Issues Count
151 | 
152 | ### 7. Provide Next Actions
153 | 
154 | At end of report, output a concise Next Actions block:
155 | 
156 | - If CRITICAL issues exist: Recommend resolving before `/implement`
157 | - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
158 | - Provide explicit command suggestions: e.g., "Run /specify with refinement", "Run /plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
159 | 
160 | ### 8. Offer Remediation
161 | 
162 | Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
163 | 
164 | ## Operating Principles
165 | 
166 | ### Context Efficiency
167 | 
168 | - **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation
169 | - **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis
170 | - **Token-efficient output**: Limit findings table to 50 rows; summarize overflow
171 | - **Deterministic results**: Rerunning without changes should produce consistent IDs and counts
172 | 
173 | ### Analysis Guidelines
174 | 
175 | - **NEVER modify files** (this is read-only analysis)
176 | - **NEVER hallucinate missing sections** (if absent, report them accurately)
177 | - **Prioritize constitution violations** (these are always CRITICAL)
178 | - **Use examples over exhaustive rules** (cite specific instances, not generic patterns)
179 | - **Report zero issues gracefully** (emit success report with coverage statistics)
180 | 
181 | ## Context
182 | 
183 | $ARGUMENTS
184 | 
```

--------------------------------------------------------------------------------
/specs/001-i-want-to/spec.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Feature Specification: Retrieve Development Information from Jira Issue
  2 | 
  3 | **Feature Branch**: `001-i-want-to`
  4 | **Created**: 2025-10-07
  5 | **Status**: Draft
  6 | **Input**: User description: "i want to have tool to get all branch, merge request, from a jira issue"
  7 | 
  8 | ## User Scenarios & Testing *(mandatory)*
  9 | 
 10 | ### User Story 1 - View Linked Development Work (Priority: P1)
 11 | 
 12 | As an AI assistant user or developer, I want to retrieve all branches and merge requests associated with a specific Jira issue so that I can understand what code changes are related to that issue and their current status.
 13 | 
 14 | **Why this priority**: This is the core functionality that delivers immediate value by providing visibility into development activity linked to a Jira issue. It enables users to track code changes, review progress, and understand the implementation status of an issue.
 15 | 
 16 | **Independent Test**: Can be fully tested by requesting development information for a Jira issue that has linked branches and merge requests, and verifying that the returned data includes branch names, merge request titles, states, and URLs.
 17 | 
 18 | **Acceptance Scenarios**:
 19 | 
 20 | 1. **Given** a Jira issue with linked branches and merge requests, **When** the user requests development information for that issue, **Then** the system returns a list of all branches with their names and repositories
 21 | 2. **Given** a Jira issue with linked branches and merge requests, **When** the user requests development information for that issue, **Then** the system returns a list of all merge requests with their titles, states (open/merged/closed), and URLs
 22 | 3. **Given** a Jira issue with no linked development work, **When** the user requests development information, **Then** the system returns an empty result or clear message indicating no development work is linked
 23 | 4. **Given** an invalid issue key, **When** the user requests development information, **Then** the system returns a clear error message explaining the issue was not found
 24 | 
 25 | ---
 26 | 
 27 | ### User Story 2 - Filter Development Information by Type (Priority: P2)
 28 | 
 29 | As a user, I want to optionally filter the development information to retrieve only branches or only merge requests so that I can focus on the specific information I need.
 30 | 
 31 | **Why this priority**: This enhances usability by allowing users to retrieve targeted information, reducing noise when they only need specific types of development data.
 32 | 
 33 | **Independent Test**: Can be tested by requesting only branches for an issue that has both branches and merge requests, and verifying only branch information is returned.
 34 | 
 35 | **Acceptance Scenarios**:
 36 | 
 37 | 1. **Given** a Jira issue with both branches and merge requests, **When** the user requests only branches, **Then** the system returns only branch information and excludes merge request data
 38 | 2. **Given** a Jira issue with both branches and merge requests, **When** the user requests only merge requests, **Then** the system returns only merge request information and excludes branch data
 39 | 3. **Given** a Jira issue with development work, **When** the user requests all development information (no filter), **Then** the system returns both branches and merge requests
 40 | 
 41 | ---
 42 | 
 43 | ### User Story 3 - View Commit Information (Priority: P3)
 44 | 
 45 | As a user, I want to see commits associated with a Jira issue so that I can understand the specific code changes that have been made.
 46 | 
 47 | **Why this priority**: This provides additional detail about the work done, but branches and merge requests are more commonly used for tracking development progress at a high level.
 48 | 
 49 | **Independent Test**: Can be tested by requesting development information for an issue with linked commits, and verifying commit messages, authors, and timestamps are returned.
 50 | 
 51 | **Acceptance Scenarios**:
 52 | 
 53 | 1. **Given** a Jira issue with linked commits, **When** the user requests development information including commits, **Then** the system returns commit messages, authors, dates, and commit IDs
 54 | 2. **Given** commits from multiple repositories, **When** the user requests development information, **Then** commits are grouped or labeled by repository
 55 | 
 56 | ---
 57 | 
 58 | ### Edge Cases
 59 | 
 60 | - What happens when a Jira issue has development information from multiple repositories or Git hosting services (GitHub, GitLab, Bitbucket)?
 61 | - How does the system handle issues where development information exists but the user lacks permissions to view the linked repositories?
 62 | - What happens when branch or merge request data is incomplete or the external Git service is unavailable?
 63 | - How does the system handle very large numbers of branches or merge requests (e.g., 50+ branches)?
 64 | - What happens when development information includes both Cloud and Data Center Jira instances?
 65 | 
 66 | ## Requirements *(mandatory)*
 67 | 
 68 | ### Functional Requirements
 69 | 
 70 | - **FR-001**: System MUST expose a `jira_get_development_information` tool that accepts an issue key parameter
 71 | - **FR-002**: Tool MUST retrieve branches linked to the specified Jira issue, including branch name, repository name, and repository URL
 72 | - **FR-003**: Tool MUST retrieve merge requests (pull requests) linked to the specified Jira issue, including title, state, URL, author, and creation date
 73 | - **FR-004**: Tool MUST retrieve commit information linked to the specified Jira issue, including commit message, author, timestamp, and commit ID
 74 | - **FR-005**: Tool MUST validate that the issue_key parameter is provided and follows valid Jira issue key format (e.g., PROJ-123)
 75 | - **FR-006**: Tool MUST return human-readable formatted text suitable for LLM consumption, organizing information by type (branches, merge requests, commits)
 76 | - **FR-007**: Tool MUST handle errors gracefully, including invalid issue keys, non-existent issues, permission errors, and API failures
 77 | - **FR-008**: Tool MUST support optional filtering parameters to retrieve only specific types of development information (branches, merge requests, or commits)
 78 | - **FR-009**: System MUST reuse the singleton Jira client connection established by the services package
 79 | - **FR-010**: Tool MUST handle cases where no development information is linked to an issue by returning a clear message
 80 | - **FR-011**: System MUST use Jira's standard `/rest/dev-status/1.0/issue/detail` endpoint via the go-atlassian client to retrieve development information
 81 | 
 82 | ### Key Entities
 83 | 
 84 | - **Development Information**: Aggregated data about code development activity related to a Jira issue, including branches, merge requests, and commits
 85 | - **Branch**: A Git branch linked to a Jira issue, containing name, repository identifier, and URL
 86 | - **Merge Request**: A pull request or merge request linked to a Jira issue, containing title, state (open/merged/closed/declined), URL, author, and timestamps
 87 | - **Commit**: A Git commit linked to a Jira issue, containing message, author, timestamp, commit ID, and repository information
 88 | - **Repository**: The source code repository where branches, commits, and merge requests exist, identified by name, URL, and hosting service type
 89 | 
 90 | ## Success Criteria *(mandatory)*
 91 | 
 92 | ### Measurable Outcomes
 93 | 
 94 | - **SC-001**: Users can retrieve complete development information for a Jira issue in a single tool call
 95 | - **SC-002**: The tool returns results within 3 seconds for issues with up to 50 linked development items
 96 | - **SC-003**: The formatted output clearly distinguishes between branches, merge requests, and commits with appropriate grouping and labels
 97 | - **SC-004**: 100% of valid Jira issue keys return either development information or a clear "no development work linked" message
 98 | - **SC-005**: Error messages clearly identify the issue (invalid key format, issue not found, permission denied, API error) in 100% of failure cases
 99 | - **SC-006**: The tool successfully handles development information from all major Git hosting services (GitHub, GitLab, Bitbucket) supported by Jira
100 | 
101 | ## Assumptions
102 | 
103 | - The Atlassian instance has development tool integrations configured (e.g., GitHub, GitLab, Bitbucket integrations enabled)
104 | - Users have appropriate Jira permissions to view development information for the issues they query
105 | - The go-atlassian library provides access to Jira's development information APIs
106 | - Development information is retrieved from Jira's stored data, not by directly querying Git hosting services
107 | - The default behavior (when no filter is specified) is to return all types of development information
108 | - Branch and merge request URLs point to the external Git hosting service and are provided by Jira's development information API
109 | 
```

--------------------------------------------------------------------------------
/util/jira_formatter.go:
--------------------------------------------------------------------------------

```go
  1 | package util
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"strings"
  6 | 
  7 | 	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
  8 | )
  9 | 
 10 | // FormatJiraIssue converts a Jira issue struct to a formatted string representation
 11 | // It handles all available fields from IssueFieldsSchemeV2 and related schemas
 12 | func FormatJiraIssue(issue *models.IssueScheme) string {
 13 | 	var sb strings.Builder
 14 | 
 15 | 	// Basic issue information
 16 | 	sb.WriteString(fmt.Sprintf("Key: %s\n", issue.Key))
 17 | 	
 18 | 	if issue.ID != "" {
 19 | 		sb.WriteString(fmt.Sprintf("ID: %s\n", issue.ID))
 20 | 	}
 21 | 	
 22 | 	if issue.Self != "" {
 23 | 		sb.WriteString(fmt.Sprintf("URL: %s\n", issue.Self))
 24 | 	}
 25 | 
 26 | 	// Fields information
 27 | 	if issue.Fields != nil {
 28 | 		fields := issue.Fields
 29 | 
 30 | 		// Summary and Description
 31 | 		if fields.Summary != "" {
 32 | 			sb.WriteString(fmt.Sprintf("Summary: %s\n", fields.Summary))
 33 | 		}
 34 | 
 35 | 		if fields.Description != nil {
 36 | 			sb.WriteString(fmt.Sprintf("Description: %s\n", fields.Description))
 37 | 		}
 38 | 
 39 | 		// Issue Type
 40 | 		if fields.IssueType != nil {
 41 | 			sb.WriteString(fmt.Sprintf("Type: %s\n", fields.IssueType.Name))
 42 | 			if fields.IssueType.Description != "" {
 43 | 				sb.WriteString(fmt.Sprintf("Type Description: %s\n", fields.IssueType.Description))
 44 | 			}
 45 | 		}
 46 | 
 47 | 		// Status
 48 | 		if fields.Status != nil {
 49 | 			sb.WriteString(fmt.Sprintf("Status: %s\n", fields.Status.Name))
 50 | 			if fields.Status.Description != "" {
 51 | 				sb.WriteString(fmt.Sprintf("Status Description: %s\n", fields.Status.Description))
 52 | 			}
 53 | 		}
 54 | 
 55 | 		// Priority
 56 | 		if fields.Priority != nil {
 57 | 			sb.WriteString(fmt.Sprintf("Priority: %s\n", fields.Priority.Name))
 58 | 		} else {
 59 | 			sb.WriteString("Priority: None\n")
 60 | 		}
 61 | 
 62 | 		// Resolution
 63 | 		if fields.Resolution != nil {
 64 | 			sb.WriteString(fmt.Sprintf("Resolution: %s\n", fields.Resolution.Name))
 65 | 			if fields.Resolution.Description != "" {
 66 | 				sb.WriteString(fmt.Sprintf("Resolution Description: %s\n", fields.Resolution.Description))
 67 | 			}
 68 | 		}
 69 | 
 70 | 		// Resolution Date
 71 | 		if fields.Resolutiondate != "" {
 72 | 			sb.WriteString(fmt.Sprintf("Resolution Date: %s\n", fields.Resolutiondate))
 73 | 		}
 74 | 
 75 | 		// People
 76 | 		if fields.Reporter != nil {
 77 | 			sb.WriteString(fmt.Sprintf("Reporter: %s", fields.Reporter.DisplayName))
 78 | 			if fields.Reporter.EmailAddress != "" {
 79 | 				sb.WriteString(fmt.Sprintf(" (%s)", fields.Reporter.EmailAddress))
 80 | 			}
 81 | 			sb.WriteString("\n")
 82 | 		} else {
 83 | 			sb.WriteString("Reporter: Unassigned\n")
 84 | 		}
 85 | 
 86 | 		if fields.Assignee != nil {
 87 | 			sb.WriteString(fmt.Sprintf("Assignee: %s", fields.Assignee.DisplayName))
 88 | 			if fields.Assignee.EmailAddress != "" {
 89 | 				sb.WriteString(fmt.Sprintf(" (%s)", fields.Assignee.EmailAddress))
 90 | 			}
 91 | 			sb.WriteString("\n")
 92 | 		} else {
 93 | 			sb.WriteString("Assignee: Unassigned\n")
 94 | 		}
 95 | 
 96 | 		if fields.Creator != nil {
 97 | 			sb.WriteString(fmt.Sprintf("Creator: %s", fields.Creator.DisplayName))
 98 | 			if fields.Creator.EmailAddress != "" {
 99 | 				sb.WriteString(fmt.Sprintf(" (%s)", fields.Creator.EmailAddress))
100 | 			}
101 | 			sb.WriteString("\n")
102 | 		}
103 | 
104 | 		// Dates
105 | 		if fields.Created != "" {
106 | 			sb.WriteString(fmt.Sprintf("Created: %s\n", fields.Created))
107 | 		}
108 | 
109 | 		if fields.Updated != "" {
110 | 			sb.WriteString(fmt.Sprintf("Updated: %s\n", fields.Updated))
111 | 		}
112 | 
113 | 		if fields.LastViewed != "" {
114 | 			sb.WriteString(fmt.Sprintf("Last Viewed: %s\n", fields.LastViewed))
115 | 		}
116 | 
117 | 		if fields.StatusCategoryChangeDate != "" {
118 | 			sb.WriteString(fmt.Sprintf("Status Category Change Date: %s\n", fields.StatusCategoryChangeDate))
119 | 		}
120 | 
121 | 		// Project information
122 | 		if fields.Project != nil {
123 | 			sb.WriteString(fmt.Sprintf("Project: %s", fields.Project.Name))
124 | 			if fields.Project.Key != "" {
125 | 				sb.WriteString(fmt.Sprintf(" (%s)", fields.Project.Key))
126 | 			}
127 | 			sb.WriteString("\n")
128 | 		}
129 | 
130 | 		// Parent issue
131 | 		if fields.Parent != nil {
132 | 			sb.WriteString(fmt.Sprintf("Parent: %s", fields.Parent.Key))
133 | 			if fields.Parent.Fields != nil && fields.Parent.Fields.Summary != "" {
134 | 				sb.WriteString(fmt.Sprintf(" - %s", fields.Parent.Fields.Summary))
135 | 			}
136 | 			sb.WriteString("\n")
137 | 		}
138 | 
139 | 		// Work information
140 | 		if fields.Workratio > 0 {
141 | 			sb.WriteString(fmt.Sprintf("Work Ratio: %d\n", fields.Workratio))
142 | 		}
143 | 
144 | 		// Labels
145 | 		if len(fields.Labels) > 0 {
146 | 			sb.WriteString(fmt.Sprintf("Labels: %s\n", strings.Join(fields.Labels, ", ")))
147 | 		}
148 | 
149 | 		// Components
150 | 		if len(fields.Components) > 0 {
151 | 			sb.WriteString("Components:\n")
152 | 			for _, component := range fields.Components {
153 | 				sb.WriteString(fmt.Sprintf("- %s", component.Name))
154 | 				if component.Description != "" {
155 | 					sb.WriteString(fmt.Sprintf(" (%s)", component.Description))
156 | 				}
157 | 				sb.WriteString("\n")
158 | 			}
159 | 		}
160 | 
161 | 		// Fix Versions
162 | 		if len(fields.FixVersions) > 0 {
163 | 			sb.WriteString("Fix Versions:\n")
164 | 			for _, version := range fields.FixVersions {
165 | 				sb.WriteString(fmt.Sprintf("- %s", version.Name))
166 | 				if version.Description != "" {
167 | 					sb.WriteString(fmt.Sprintf(" (%s)", version.Description))
168 | 				}
169 | 				sb.WriteString("\n")
170 | 			}
171 | 		}
172 | 
173 | 		// Affected Versions
174 | 		if len(fields.Versions) > 0 {
175 | 			sb.WriteString("Affected Versions:\n")
176 | 			for _, version := range fields.Versions {
177 | 				sb.WriteString(fmt.Sprintf("- %s", version.Name))
178 | 				if version.Description != "" {
179 | 					sb.WriteString(fmt.Sprintf(" (%s)", version.Description))
180 | 				}
181 | 				sb.WriteString("\n")
182 | 			}
183 | 		}
184 | 
185 | 		// Security Level
186 | 		if fields.Security != nil {
187 | 			sb.WriteString(fmt.Sprintf("Security Level: %s\n", fields.Security.Name))
188 | 		}
189 | 
190 | 		// Subtasks
191 | 		if len(fields.Subtasks) > 0 {
192 | 			sb.WriteString("Subtasks:\n")
193 | 			for _, subtask := range fields.Subtasks {
194 | 				sb.WriteString(fmt.Sprintf("- %s", subtask.Key))
195 | 				if subtask.Fields != nil && subtask.Fields.Summary != "" {
196 | 					sb.WriteString(fmt.Sprintf(": %s", subtask.Fields.Summary))
197 | 				}
198 | 				if subtask.Fields != nil && subtask.Fields.Status != nil {
199 | 					sb.WriteString(fmt.Sprintf(" [%s]", subtask.Fields.Status.Name))
200 | 				}
201 | 				sb.WriteString("\n")
202 | 			}
203 | 		}
204 | 
205 | 		// Issue Links
206 | 		if len(fields.IssueLinks) > 0 {
207 | 			sb.WriteString("Issue Links:\n")
208 | 			for _, link := range fields.IssueLinks {
209 | 				if link.OutwardIssue != nil {
210 | 					sb.WriteString(fmt.Sprintf("- %s %s", link.Type.Outward, link.OutwardIssue.Key))
211 | 					if link.OutwardIssue.Fields != nil && link.OutwardIssue.Fields.Summary != "" {
212 | 						sb.WriteString(fmt.Sprintf(": %s", link.OutwardIssue.Fields.Summary))
213 | 					}
214 | 					sb.WriteString("\n")
215 | 				}
216 | 				if link.InwardIssue != nil {
217 | 					sb.WriteString(fmt.Sprintf("- %s %s", link.Type.Inward, link.InwardIssue.Key))
218 | 					if link.InwardIssue.Fields != nil && link.InwardIssue.Fields.Summary != "" {
219 | 						sb.WriteString(fmt.Sprintf(": %s", link.InwardIssue.Fields.Summary))
220 | 					}
221 | 					sb.WriteString("\n")
222 | 				}
223 | 			}
224 | 		}
225 | 
226 | 		// Watchers
227 | 		if fields.Watcher != nil {
228 | 			sb.WriteString(fmt.Sprintf("Watchers: %d\n", fields.Watcher.WatchCount))
229 | 		}
230 | 
231 | 		// Votes
232 | 		if fields.Votes != nil {
233 | 			sb.WriteString(fmt.Sprintf("Votes: %d\n", fields.Votes.Votes))
234 | 		}
235 | 
236 | 		// Comments (summary only to avoid too much text)
237 | 		if fields.Comment != nil && fields.Comment.Total > 0 {
238 | 			sb.WriteString(fmt.Sprintf("Comments: %d total\n", fields.Comment.Total))
239 | 		}
240 | 
241 | 		// Worklogs (summary only)
242 | 		if fields.Worklog != nil && fields.Worklog.Total > 0 {
243 | 			sb.WriteString(fmt.Sprintf("Worklogs: %d entries\n", fields.Worklog.Total))
244 | 		}
245 | 	}
246 | 
247 | 	// Available Transitions
248 | 	if len(issue.Transitions) > 0 {
249 | 		sb.WriteString("\nAvailable Transitions:\n")
250 | 		for _, transition := range issue.Transitions {
251 | 			sb.WriteString(fmt.Sprintf("- %s (ID: %s)\n", transition.Name, transition.ID))
252 | 		}
253 | 	}
254 | 
255 | 	// Story point estimate from changelog (if available)
256 | 	if issue.Changelog != nil && issue.Changelog.Histories != nil {
257 | 		storyPoint := ""
258 | 		for _, history := range issue.Changelog.Histories {
259 | 			for _, item := range history.Items {
260 | 				if item.Field == "Story point estimate" && item.ToString != "" {
261 | 					storyPoint = item.ToString
262 | 				}
263 | 			}
264 | 		}
265 | 		if storyPoint != "" {
266 | 			sb.WriteString(fmt.Sprintf("Story Point Estimate: %s\n", storyPoint))
267 | 		}
268 | 	}
269 | 
270 | 	return sb.String()
271 | }
272 | 
273 | // FormatJiraIssueCompact returns a compact single-line representation of a Jira issue
274 | // Useful for search results or lists
275 | func FormatJiraIssueCompact(issue *models.IssueScheme) string {
276 | 	if issue == nil {
277 | 		return ""
278 | 	}
279 | 
280 | 	var parts []string
281 | 	
282 | 	parts = append(parts, fmt.Sprintf("Key: %s", issue.Key))
283 | 	
284 | 	if issue.Fields != nil {
285 | 		fields := issue.Fields
286 | 		
287 | 		if fields.Summary != "" {
288 | 			parts = append(parts, fmt.Sprintf("Summary: %s", fields.Summary))
289 | 		}
290 | 		
291 | 		if fields.Status != nil {
292 | 			parts = append(parts, fmt.Sprintf("Status: %s", fields.Status.Name))
293 | 		}
294 | 		
295 | 		if fields.Assignee != nil {
296 | 			parts = append(parts, fmt.Sprintf("Assignee: %s", fields.Assignee.DisplayName))
297 | 		} else {
298 | 			parts = append(parts, "Assignee: Unassigned")
299 | 		}
300 | 		
301 | 		if fields.Priority != nil {
302 | 			parts = append(parts, fmt.Sprintf("Priority: %s", fields.Priority.Name))
303 | 		}
304 | 	}
305 | 	
306 | 	return strings.Join(parts, " | ")
307 | } 
```
Page 1/3FirstPrevNextLast