This is page 2 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 -------------------------------------------------------------------------------- /specs/001-i-want-to/plan.md: -------------------------------------------------------------------------------- ```markdown 1 | # Implementation Plan: Retrieve Development Information from Jira Issue 2 | 3 | **Branch**: `001-i-want-to` | **Date**: 2025-10-07 | **Spec**: [spec.md](./spec.md) 4 | **Input**: Feature specification from `/specs/001-i-want-to/spec.md` 5 | 6 | ## Summary 7 | 8 | This feature adds a new MCP tool `jira_get_development_information` that retrieves branches, merge requests (pull requests), and commits linked to a Jira issue via Jira's Development Information API (`/rest/dev-status/1.0/issue/detail`). The tool will follow the existing pattern of typed handlers, singleton Jira client usage, and LLM-optimized text output. 9 | 10 | ## Technical Context 11 | 12 | **Language/Version**: Go 1.23.2 13 | **Primary Dependencies**: 14 | - `github.com/ctreminiom/go-atlassian v1.6.1` (Jira API client) 15 | - `github.com/mark3labs/mcp-go v0.32.0` (MCP protocol) 16 | **Storage**: N/A (stateless API wrapper) 17 | **Testing**: Go standard testing (`go test ./...`), integration tests with live Jira instance 18 | **Target Platform**: Cross-platform (macOS, Linux, Windows) via compiled Go binary 19 | **Project Type**: Single project - MCP server binary 20 | **Performance Goals**: Response within 3 seconds for issues with up to 50 linked development items 21 | **Constraints**: Relies on Jira's stored development information (GitHub, GitLab, Bitbucket integrations must be configured in Jira) 22 | **Scale/Scope**: Single new MCP tool with 3-4 optional filter parameters 23 | 24 | ## Constitution Check 25 | 26 | *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* 27 | 28 | ### Initial Check (Pre-Phase 0) ✅ 29 | 30 | **MCP Protocol Compliance:** 31 | - [x] All features exposed via MCP tools (no direct API bypass) - New tool `jira_get_development_information` 32 | - [x] Tool names follow `jira_<operation>` convention - Tool named `jira_get_development_information` 33 | - [x] STDIO mode primary, HTTP mode development-only - Inherits existing server modes 34 | 35 | **AI-First Output:** 36 | - [x] Responses formatted for LLM readability - Will use human-readable text with clear sections for branches, merge requests, commits 37 | - [x] Error messages include diagnostic context (endpoint, response body) - Following existing error handling pattern 38 | - [x] Output uses `util.Format*` functions or similar formatting - Will create `util.FormatDevelopmentInfo` if needed (only if reused 3+ times per constitution) 39 | 40 | **Simplicity:** 41 | - [x] No unnecessary abstraction layers (managers, facades, orchestrators) - Direct API call in handler 42 | - [x] Direct client calls preferred over wrappers - Handler calls `client` directly 43 | - [x] Complexity violations documented in "Complexity Tracking" below - No violations 44 | 45 | **Type Safety:** 46 | - [x] Input structs defined with JSON tags and validation - `GetDevelopmentInfoInput` struct with `issue_key` required field 47 | - [x] Typed handlers used (`mcp.NewTypedToolHandler`) - Will use `mcp.NewTypedToolHandler(jiraGetDevelopmentInfoHandler)` 48 | 49 | **Resource Efficiency:** 50 | - [x] Singleton pattern for client connections (`services.JiraClient()`) - Uses existing singleton 51 | - [x] No per-request client creation - Reuses `services.JiraClient()` 52 | 53 | **Testing Gates:** 54 | - [x] Integration tests for new tool categories - Will add test to verify tool registration and basic response 55 | - [x] Contract tests for tool registration and parameters - Will verify required parameters enforced 56 | 57 | ### Post-Phase 1 Design Review ✅ 58 | 59 | **MCP Protocol Compliance:** 60 | - [x] Tool contract defined in `contracts/mcp-tool-contract.json` with complete input schema 61 | - [x] Tool name confirmed: `jira_get_development_information` 62 | - [x] All parameters properly typed (string for issue_key, boolean for filters) 63 | 64 | **AI-First Output:** 65 | - [x] Output format designed in research.md - plain text with === section dividers 66 | - [x] Hierarchical formatting: sections for branches/PRs/commits with indentation 67 | - [x] Error messages include endpoint context (verified in contract) 68 | - [x] **CONFIRMED**: No util function - inline string builder formatting (per research Task 2) 69 | 70 | **Simplicity:** 71 | - [x] Single tool file: `tools/jira_development.go` 72 | - [x] Direct API calls using `client.NewRequest()` and `client.Call()` 73 | - [x] No wrapper services or helper classes 74 | - [x] Inline formatting functions within handler (formatBranches, formatPullRequests, formatCommits) 75 | 76 | **Type Safety:** 77 | - [x] Input struct defined in data-model.md: `GetDevelopmentInfoInput` with validate tags 78 | - [x] Response structs defined: DevStatusResponse, Branch, PullRequest, Repository, Commit, Author 79 | - [x] All JSON tags specified 80 | - [x] Validation: `validate:"required"` on issue_key field 81 | 82 | **Resource Efficiency:** 83 | - [x] Uses existing `services.JiraClient()` singleton 84 | - [x] Single API call per tool invocation (convert issue key → fetch dev info) 85 | - [x] No caching layer (keeps it simple) 86 | 87 | **Testing Gates:** 88 | - [x] Test plan documented in contract: unit tests for registration, integration tests for API calls 89 | - [x] Test file locations specified: `tools/jira_development_test.go`, `tests/development_test.go` 90 | 91 | **Design Quality:** 92 | - [x] Data model complete with entity relationships documented 93 | - [x] API contract specifies all request/response formats 94 | - [x] Quickstart guide provides usage examples 95 | - [x] Research decisions documented with rationale 96 | 97 | ### Compliance Status: ✅ PASSED 98 | 99 | All constitution principles are satisfied. No violations. Ready for `/speckit.tasks` to generate implementation tasks. 100 | 101 | ## Project Structure 102 | 103 | ### Documentation (this feature) 104 | 105 | ``` 106 | specs/001-i-want-to/ 107 | ├── plan.md # This file 108 | ├── research.md # Phase 0: API research and response format decisions 109 | ├── data-model.md # Phase 1: Development information entities 110 | ├── quickstart.md # Phase 1: Usage examples 111 | ├── contracts/ # Phase 1: API response schema 112 | │ └── dev-info-schema.json 113 | └── tasks.md # Phase 2: Implementation tasks (generated by /speckit.tasks) 114 | ``` 115 | 116 | ### Source Code (repository root) 117 | 118 | ``` 119 | # Single project structure (matches existing) 120 | tools/ 121 | ├── jira_issue.go 122 | ├── jira_comment.go 123 | ├── jira_sprint.go 124 | ├── jira_development.go # NEW: Development info tool registration and handler 125 | └── ... 126 | 127 | services/ 128 | └── jira.go # Existing: Singleton client 129 | 130 | util/ 131 | └── formatter.go # Potentially NEW: FormatDevelopmentInfo (only if complex) 132 | 133 | main.go # Updated: Register new tool 134 | 135 | tests/ # NEW: Integration test for development info tool 136 | └── development_test.go 137 | ``` 138 | 139 | **Structure Decision**: This follows the existing single-project Go structure. The new tool will be implemented in `tools/jira_development.go` following the same pattern as `tools/jira_issue.go`. No new directories or architectural changes needed. 140 | 141 | ## Complexity Tracking 142 | 143 | *No complexity violations identified. The implementation follows existing patterns with no additional abstraction layers.* 144 | 145 | ## Phase 0: Research Tasks 146 | 147 | ### Research Task 1: go-atlassian Development Information API 148 | 149 | **Objective**: Determine the exact method and response structure for retrieving development information from the go-atlassian library. 150 | 151 | **Questions to Answer**: 152 | 1. Does `go-atlassian` provide a method for `/rest/dev-status/1.0/issue/detail` endpoint? 153 | 2. What is the response structure (types, fields)? 154 | 3. How are branches, merge requests, and commits organized in the response? 155 | 4. What error cases exist (issue not found, no dev info, permission denied)? 156 | 5. Are there any pagination or rate limiting considerations? 157 | 158 | **Research Method**: 159 | - Review go-atlassian documentation and source code 160 | - Check for methods in client Issue service or Development service 161 | - Test with live Jira instance if available 162 | 163 | ### Research Task 2: Output Formatting Best Practices 164 | 165 | **Objective**: Define the optimal text format for LLM consumption of development information. 166 | 167 | **Questions to Answer**: 168 | 1. How should branches, merge requests, and commits be grouped and labeled? 169 | 2. What level of detail is appropriate for each item (full metadata vs. summary)? 170 | 3. How to handle empty results (no development info linked)? 171 | 4. What formatting makes state information (open/merged/closed) clear? 172 | 173 | **Research Method**: 174 | - Review existing `util.Format*` functions in codebase 175 | - Consider markdown-style formatting with headers and bullet lists 176 | - Design for clarity without verbose JSON 177 | 178 | ### Research Task 3: Filter Parameter Design 179 | 180 | **Objective**: Determine the optimal parameter structure for filtering by development information type. 181 | 182 | **Questions to Answer**: 183 | 1. Should filters be separate boolean flags (`include_branches`, `include_commits`) or an enum/string array? 184 | 2. What is the default behavior (all types vs. explicit opt-in)? 185 | 3. How do other Jira MCP tools handle optional filtering? 186 | 187 | **Research Method**: 188 | - Review existing tool parameters in `tools/` directory 189 | - Check go-atlassian API capabilities for filtering 190 | - Consider LLM usability (simpler is better) 191 | 192 | **Output**: `research.md` with decisions and rationale for all three research tasks. 193 | ``` -------------------------------------------------------------------------------- /tools/jira_sprint.go: -------------------------------------------------------------------------------- ```go 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "strings" 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 ListSprintsInput struct { 17 | BoardID string `json:"board_id,omitempty"` 18 | ProjectKey string `json:"project_key,omitempty"` 19 | } 20 | 21 | type GetSprintInput struct { 22 | SprintID string `json:"sprint_id" validate:"required"` 23 | } 24 | 25 | type GetActiveSprintInput struct { 26 | BoardID string `json:"board_id,omitempty"` 27 | ProjectKey string `json:"project_key,omitempty"` 28 | } 29 | 30 | type SearchSprintByNameInput struct { 31 | Name string `json:"name" validate:"required"` 32 | BoardID string `json:"board_id,omitempty"` 33 | ProjectKey string `json:"project_key,omitempty"` 34 | ExactMatch bool `json:"exact_match,omitempty"` 35 | } 36 | 37 | func RegisterJiraSprintTool(s *server.MCPServer) { 38 | jiraListSprintTool := mcp.NewTool("jira_list_sprints", 39 | mcp.WithDescription("List all active and future sprints for a specific Jira board or project. Requires either board_id or project_key."), 40 | mcp.WithString("board_id", mcp.Description("Numeric ID of the Jira board (can be found in board URL). Optional if project_key is provided.")), 41 | mcp.WithString("project_key", mcp.Description("The project key (e.g., KP, PROJ, DEV). Optional if board_id is provided.")), 42 | ) 43 | s.AddTool(jiraListSprintTool, mcp.NewTypedToolHandler(jiraListSprintHandler)) 44 | 45 | jiraGetSprintTool := mcp.NewTool("jira_get_sprint", 46 | mcp.WithDescription("Retrieve detailed information about a specific Jira sprint by its ID"), 47 | mcp.WithString("sprint_id", mcp.Required(), mcp.Description("Numeric ID of the sprint to retrieve")), 48 | ) 49 | s.AddTool(jiraGetSprintTool, mcp.NewTypedToolHandler(jiraGetSprintHandler)) 50 | 51 | jiraGetActiveSprintTool := mcp.NewTool("jira_get_active_sprint", 52 | mcp.WithDescription("Get the currently active sprint for a given board or project. Requires either board_id or project_key."), 53 | mcp.WithString("board_id", mcp.Description("Numeric ID of the Jira board. Optional if project_key is provided.")), 54 | mcp.WithString("project_key", mcp.Description("The project key (e.g., KP, PROJ, DEV). Optional if board_id is provided.")), 55 | ) 56 | s.AddTool(jiraGetActiveSprintTool, mcp.NewTypedToolHandler(jiraGetActiveSprintHandler)) 57 | 58 | jiraSearchSprintByNameTool := mcp.NewTool("jira_search_sprint_by_name", 59 | mcp.WithDescription("Search for sprints by name across boards or projects. Supports both exact and partial name matching."), 60 | mcp.WithString("name", mcp.Required(), mcp.Description("Sprint name to search for (case-insensitive)")), 61 | mcp.WithString("board_id", mcp.Description("Numeric ID of the Jira board to search in. Optional if project_key is provided.")), 62 | mcp.WithString("project_key", mcp.Description("The project key (e.g., KP, PROJ, DEV) to search in. Optional if board_id is provided.")), 63 | mcp.WithBoolean("exact_match", mcp.Description("If true, only return sprints with exact name match. Default is false (partial matching).")), 64 | ) 65 | s.AddTool(jiraSearchSprintByNameTool, mcp.NewTypedToolHandler(searchSprintByNameHandler)) 66 | } 67 | 68 | // Helper function to get board IDs either from direct board_id or by finding boards for a project 69 | func getBoardIDsFromInput(ctx context.Context, boardID, projectKey string) ([]int, error) { 70 | if boardID == "" && projectKey == "" { 71 | return nil, fmt.Errorf("either board_id or project_key argument is required") 72 | } 73 | 74 | if boardID != "" { 75 | boardIDInt, err := strconv.Atoi(boardID) 76 | if err != nil { 77 | return nil, fmt.Errorf("invalid board_id: %v", err) 78 | } 79 | return []int{boardIDInt}, nil 80 | } 81 | 82 | if projectKey != "" { 83 | boards, response, err := services.AgileClient().Board.Gets(ctx, &models.GetBoardsOptions{ 84 | ProjectKeyOrID: projectKey, 85 | }, 0, 50) 86 | if err != nil { 87 | if response != nil { 88 | return nil, fmt.Errorf("failed to get boards: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 89 | } 90 | return nil, fmt.Errorf("failed to get boards: %v", err) 91 | } 92 | 93 | if len(boards.Values) == 0 { 94 | return nil, fmt.Errorf("no boards found for project: %s", projectKey) 95 | } 96 | 97 | var boardIDs []int 98 | for _, board := range boards.Values { 99 | boardIDs = append(boardIDs, board.ID) 100 | } 101 | return boardIDs, nil 102 | } 103 | 104 | return nil, fmt.Errorf("either board_id or project_key argument is required") 105 | } 106 | 107 | func jiraGetSprintHandler(ctx context.Context, request mcp.CallToolRequest, input GetSprintInput) (*mcp.CallToolResult, error) { 108 | sprintID, err := strconv.Atoi(input.SprintID) 109 | if err != nil { 110 | return nil, fmt.Errorf("invalid sprint_id: %v", err) 111 | } 112 | 113 | sprint, response, err := services.AgileClient().Sprint.Get(ctx, sprintID) 114 | if err != nil { 115 | if response != nil { 116 | return nil, fmt.Errorf("failed to get sprint: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 117 | } 118 | return nil, fmt.Errorf("failed to get sprint: %v", err) 119 | } 120 | 121 | result := fmt.Sprintf(`Sprint Details: 122 | ID: %d 123 | Name: %s 124 | State: %s 125 | Start Date: %s 126 | End Date: %s 127 | Complete Date: %s 128 | Origin Board ID: %d 129 | Goal: %s`, 130 | sprint.ID, 131 | sprint.Name, 132 | sprint.State, 133 | sprint.StartDate, 134 | sprint.EndDate, 135 | sprint.CompleteDate, 136 | sprint.OriginBoardID, 137 | sprint.Goal, 138 | ) 139 | 140 | return mcp.NewToolResultText(result), nil 141 | } 142 | 143 | func jiraListSprintHandler(ctx context.Context, request mcp.CallToolRequest, input ListSprintsInput) (*mcp.CallToolResult, error) { 144 | boardIDs, err := getBoardIDsFromInput(ctx, input.BoardID, input.ProjectKey) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | var allSprints []string 150 | for _, boardID := range boardIDs { 151 | sprints, response, err := services.AgileClient().Board.Sprints(ctx, boardID, 0, 50, []string{"active", "future"}) 152 | if err != nil { 153 | if response != nil { 154 | return nil, fmt.Errorf("failed to get sprints: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 155 | } 156 | return nil, fmt.Errorf("failed to get sprints: %v", err) 157 | } 158 | 159 | for _, sprint := range sprints.Values { 160 | allSprints = append(allSprints, fmt.Sprintf("ID: %d\nName: %s\nState: %s\nStartDate: %s\nEndDate: %s\nBoard ID: %d\n", 161 | sprint.ID, sprint.Name, sprint.State, sprint.StartDate, sprint.EndDate, boardID)) 162 | } 163 | } 164 | 165 | if len(allSprints) == 0 { 166 | return mcp.NewToolResultText("No sprints found."), nil 167 | } 168 | 169 | result := strings.Join(allSprints, "\n") 170 | return mcp.NewToolResultText(result), nil 171 | } 172 | 173 | func jiraGetActiveSprintHandler(ctx context.Context, request mcp.CallToolRequest, input GetActiveSprintInput) (*mcp.CallToolResult, error) { 174 | boardIDs, err := getBoardIDsFromInput(ctx, input.BoardID, input.ProjectKey) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | // Loop through boards and return the first active sprint found 180 | for _, boardID := range boardIDs { 181 | sprints, response, err := services.AgileClient().Board.Sprints(ctx, boardID, 0, 50, []string{"active"}) 182 | if err != nil { 183 | if response != nil { 184 | return nil, fmt.Errorf("failed to get active sprint: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 185 | } 186 | continue // Try next board if this one fails 187 | } 188 | 189 | if len(sprints.Values) > 0 { 190 | sprint := sprints.Values[0] 191 | result := fmt.Sprintf(`Active Sprint: 192 | ID: %d 193 | Name: %s 194 | State: %s 195 | Start Date: %s 196 | End Date: %s 197 | Board ID: %d`, 198 | sprint.ID, 199 | sprint.Name, 200 | sprint.State, 201 | sprint.StartDate, 202 | sprint.EndDate, 203 | boardID, 204 | ) 205 | return mcp.NewToolResultText(result), nil 206 | } 207 | } 208 | 209 | return mcp.NewToolResultText("No active sprint found."), nil 210 | } 211 | 212 | func searchSprintByNameHandler(ctx context.Context, request mcp.CallToolRequest, input SearchSprintByNameInput) (*mcp.CallToolResult, error) { 213 | boardIDs, err := getBoardIDsFromInput(ctx, input.BoardID, input.ProjectKey) 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | var matchingSprints []string 219 | searchTerm := strings.ToLower(input.Name) 220 | 221 | for _, boardID := range boardIDs { 222 | // Get all sprints (active, future, and closed) for comprehensive search 223 | sprints, response, err := services.AgileClient().Board.Sprints(ctx, boardID, 0, 100, []string{"active", "future", "closed"}) 224 | if err != nil { 225 | if response != nil { 226 | return nil, fmt.Errorf("failed to get sprints: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 227 | } 228 | return nil, fmt.Errorf("failed to get sprints: %v", err) 229 | } 230 | 231 | for _, sprint := range sprints.Values { 232 | sprintNameLower := strings.ToLower(sprint.Name) 233 | 234 | var isMatch bool 235 | if input.ExactMatch { 236 | isMatch = sprintNameLower == searchTerm 237 | } else { 238 | isMatch = strings.Contains(sprintNameLower, searchTerm) 239 | } 240 | 241 | if isMatch { 242 | matchingSprints = append(matchingSprints, fmt.Sprintf(`ID: %d 243 | Name: %s 244 | State: %s 245 | Start Date: %s 246 | End Date: %s 247 | Complete Date: %s 248 | Board ID: %d 249 | Goal: %s`, 250 | sprint.ID, 251 | sprint.Name, 252 | sprint.State, 253 | sprint.StartDate, 254 | sprint.EndDate, 255 | sprint.CompleteDate, 256 | boardID, 257 | sprint.Goal)) 258 | } 259 | } 260 | } 261 | 262 | if len(matchingSprints) == 0 { 263 | matchType := "containing" 264 | if input.ExactMatch { 265 | matchType = "with exact name" 266 | } 267 | return mcp.NewToolResultText(fmt.Sprintf("No sprints found %s '%s'.", matchType, input.Name)), nil 268 | } 269 | 270 | result := strings.Join(matchingSprints, "\n\n") 271 | return mcp.NewToolResultText(result), nil 272 | } 273 | ``` -------------------------------------------------------------------------------- /.specify/templates/tasks-template.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | description: "Task list template for feature implementation" 3 | --- 4 | 5 | # Tasks: [FEATURE NAME] 6 | 7 | **Input**: Design documents from `/specs/[###-feature-name]/` 8 | **Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ 9 | 10 | **Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification. 11 | 12 | **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. 13 | 14 | ## Format: `[ID] [P?] [Story] Description` 15 | - **[P]**: Can run in parallel (different files, no dependencies) 16 | - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) 17 | - Include exact file paths in descriptions 18 | 19 | ## Path Conventions 20 | - **Single project**: `src/`, `tests/` at repository root 21 | - **Web app**: `backend/src/`, `frontend/src/` 22 | - **Mobile**: `api/src/`, `ios/src/` or `android/src/` 23 | - Paths shown below assume single project - adjust based on plan.md structure 24 | 25 | <!-- 26 | ============================================================================ 27 | IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only. 28 | 29 | The /speckit.tasks command MUST replace these with actual tasks based on: 30 | - User stories from spec.md (with their priorities P1, P2, P3...) 31 | - Feature requirements from plan.md 32 | - Entities from data-model.md 33 | - Endpoints from contracts/ 34 | 35 | Tasks MUST be organized by user story so each story can be: 36 | - Implemented independently 37 | - Tested independently 38 | - Delivered as an MVP increment 39 | 40 | DO NOT keep these sample tasks in the generated tasks.md file. 41 | ============================================================================ 42 | --> 43 | 44 | ## Phase 1: Setup (Shared Infrastructure) 45 | 46 | **Purpose**: Project initialization and basic structure 47 | 48 | - [ ] T001 Create project structure per implementation plan 49 | - [ ] T002 Initialize Go module with MCP dependencies (`github.com/mark3labs/mcp-go`, `github.com/ctreminiom/go-atlassian`) 50 | - [ ] T003 [P] Configure linting and formatting tools (golangci-lint, gofmt) 51 | 52 | --- 53 | 54 | ## Phase 2: Foundational (Blocking Prerequisites) 55 | 56 | **Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented 57 | 58 | **⚠️ CRITICAL**: No user story work can begin until this phase is complete 59 | 60 | Examples of foundational tasks (adjust based on your project): 61 | 62 | - [ ] T004 Setup database schema and migrations framework 63 | - [ ] T005 [P] Implement authentication/authorization framework 64 | - [ ] T006 [P] Setup API routing and middleware structure 65 | - [ ] T007 Create base models/entities that all stories depend on 66 | - [ ] T008 Configure error handling and logging infrastructure 67 | - [ ] T009 Setup environment configuration management 68 | 69 | **Checkpoint**: Foundation ready - user story implementation can now begin in parallel 70 | 71 | --- 72 | 73 | ## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP 74 | 75 | **Goal**: [Brief description of what this story delivers] 76 | 77 | **Independent Test**: [How to verify this story works on its own] 78 | 79 | ### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️ 80 | 81 | **NOTE: Write these tests FIRST, ensure they FAIL before implementation** 82 | 83 | - [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py 84 | - [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py 85 | 86 | ### Implementation for User Story 1 87 | 88 | - [ ] T012 [P] [US1] Define `<Operation>Input` struct with JSON tags and validation in tools/jira_[category].go 89 | - [ ] T013 [US1] Implement `RegisterJira<Category>Tool` registration function in tools/jira_[category].go 90 | - [ ] T014 [US1] Implement `jira<Operation>Handler` typed handler in tools/jira_[category].go 91 | - [ ] T015 [US1] Add formatter function in util/jira_formatter.go (if output used by 3+ tools) 92 | - [ ] T016 [US1] Register tool in main.go via `tools.RegisterJira<Category>Tool(mcpServer)` 93 | - [ ] T017 [US1] Add integration test in tools/jira_[category]_test.go 94 | 95 | **Checkpoint**: At this point, User Story 1 should be fully functional and testable independently 96 | 97 | --- 98 | 99 | ## Phase 4: User Story 2 - [Title] (Priority: P2) 100 | 101 | **Goal**: [Brief description of what this story delivers] 102 | 103 | **Independent Test**: [How to verify this story works on its own] 104 | 105 | ### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ 106 | 107 | - [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py 108 | - [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py 109 | 110 | ### Implementation for User Story 2 111 | 112 | - [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py 113 | - [ ] T021 [US2] Implement [Service] in src/services/[service].py 114 | - [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py 115 | - [ ] T023 [US2] Integrate with User Story 1 components (if needed) 116 | 117 | **Checkpoint**: At this point, User Stories 1 AND 2 should both work independently 118 | 119 | --- 120 | 121 | ## Phase 5: User Story 3 - [Title] (Priority: P3) 122 | 123 | **Goal**: [Brief description of what this story delivers] 124 | 125 | **Independent Test**: [How to verify this story works on its own] 126 | 127 | ### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ 128 | 129 | - [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py 130 | - [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py 131 | 132 | ### Implementation for User Story 3 133 | 134 | - [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py 135 | - [ ] T027 [US3] Implement [Service] in src/services/[service].py 136 | - [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py 137 | 138 | **Checkpoint**: All user stories should now be independently functional 139 | 140 | --- 141 | 142 | [Add more user story phases as needed, following the same pattern] 143 | 144 | --- 145 | 146 | ## Phase N: Polish & Cross-Cutting Concerns 147 | 148 | **Purpose**: Improvements that affect multiple user stories 149 | 150 | - [ ] TXXX [P] Documentation updates in docs/ 151 | - [ ] TXXX Code cleanup and refactoring 152 | - [ ] TXXX Performance optimization across all stories 153 | - [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/ 154 | - [ ] TXXX Security hardening 155 | - [ ] TXXX Run quickstart.md validation 156 | 157 | --- 158 | 159 | ## Dependencies & Execution Order 160 | 161 | ### Phase Dependencies 162 | 163 | - **Setup (Phase 1)**: No dependencies - can start immediately 164 | - **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories 165 | - **User Stories (Phase 3+)**: All depend on Foundational phase completion 166 | - User stories can then proceed in parallel (if staffed) 167 | - Or sequentially in priority order (P1 → P2 → P3) 168 | - **Polish (Final Phase)**: Depends on all desired user stories being complete 169 | 170 | ### User Story Dependencies 171 | 172 | - **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories 173 | - **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable 174 | - **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable 175 | 176 | ### Within Each User Story 177 | 178 | - Tests (if included) MUST be written and FAIL before implementation 179 | - Models before services 180 | - Services before endpoints 181 | - Core implementation before integration 182 | - Story complete before moving to next priority 183 | 184 | ### Parallel Opportunities 185 | 186 | - All Setup tasks marked [P] can run in parallel 187 | - All Foundational tasks marked [P] can run in parallel (within Phase 2) 188 | - Once Foundational phase completes, all user stories can start in parallel (if team capacity allows) 189 | - All tests for a user story marked [P] can run in parallel 190 | - Models within a story marked [P] can run in parallel 191 | - Different user stories can be worked on in parallel by different team members 192 | 193 | --- 194 | 195 | ## Parallel Example: User Story 1 196 | 197 | ```bash 198 | # Launch all tests for User Story 1 together (if tests requested): 199 | Task: "Contract test for [endpoint] in tests/contract/test_[name].py" 200 | Task: "Integration test for [user journey] in tests/integration/test_[name].py" 201 | 202 | # Launch all models for User Story 1 together: 203 | Task: "Create [Entity1] model in src/models/[entity1].py" 204 | Task: "Create [Entity2] model in src/models/[entity2].py" 205 | ``` 206 | 207 | --- 208 | 209 | ## Implementation Strategy 210 | 211 | ### MVP First (User Story 1 Only) 212 | 213 | 1. Complete Phase 1: Setup 214 | 2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) 215 | 3. Complete Phase 3: User Story 1 216 | 4. **STOP and VALIDATE**: Test User Story 1 independently 217 | 5. Deploy/demo if ready 218 | 219 | ### Incremental Delivery 220 | 221 | 1. Complete Setup + Foundational → Foundation ready 222 | 2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) 223 | 3. Add User Story 2 → Test independently → Deploy/Demo 224 | 4. Add User Story 3 → Test independently → Deploy/Demo 225 | 5. Each story adds value without breaking previous stories 226 | 227 | ### Parallel Team Strategy 228 | 229 | With multiple developers: 230 | 231 | 1. Team completes Setup + Foundational together 232 | 2. Once Foundational is done: 233 | - Developer A: User Story 1 234 | - Developer B: User Story 2 235 | - Developer C: User Story 3 236 | 3. Stories complete and integrate independently 237 | 238 | --- 239 | 240 | ## Notes 241 | 242 | - [P] tasks = different files, no dependencies 243 | - [Story] label maps task to specific user story for traceability 244 | - Each user story should be independently completable and testable 245 | - Verify tests fail before implementing 246 | - Commit after each task or logical group 247 | - Stop at any checkpoint to validate story independently 248 | - Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence 249 | 250 | 251 | ``` -------------------------------------------------------------------------------- /.claude/commands/speckit.clarify.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec. 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 | Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. 16 | 17 | Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/speckit.plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. 18 | 19 | Execution steps: 20 | 21 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: 22 | - `FEATURE_DIR` 23 | - `FEATURE_SPEC` 24 | - (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.) 25 | - If JSON parsing fails, abort and instruct user to re-run `/speckit.specify` or verify feature branch environment. 26 | 27 | 2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked). 28 | 29 | Functional Scope & Behavior: 30 | - Core user goals & success criteria 31 | - Explicit out-of-scope declarations 32 | - User roles / personas differentiation 33 | 34 | Domain & Data Model: 35 | - Entities, attributes, relationships 36 | - Identity & uniqueness rules 37 | - Lifecycle/state transitions 38 | - Data volume / scale assumptions 39 | 40 | Interaction & UX Flow: 41 | - Critical user journeys / sequences 42 | - Error/empty/loading states 43 | - Accessibility or localization notes 44 | 45 | Non-Functional Quality Attributes: 46 | - Performance (latency, throughput targets) 47 | - Scalability (horizontal/vertical, limits) 48 | - Reliability & availability (uptime, recovery expectations) 49 | - Observability (logging, metrics, tracing signals) 50 | - Security & privacy (authN/Z, data protection, threat assumptions) 51 | - Compliance / regulatory constraints (if any) 52 | 53 | Integration & External Dependencies: 54 | - External services/APIs and failure modes 55 | - Data import/export formats 56 | - Protocol/versioning assumptions 57 | 58 | Edge Cases & Failure Handling: 59 | - Negative scenarios 60 | - Rate limiting / throttling 61 | - Conflict resolution (e.g., concurrent edits) 62 | 63 | Constraints & Tradeoffs: 64 | - Technical constraints (language, storage, hosting) 65 | - Explicit tradeoffs or rejected alternatives 66 | 67 | Terminology & Consistency: 68 | - Canonical glossary terms 69 | - Avoided synonyms / deprecated terms 70 | 71 | Completion Signals: 72 | - Acceptance criteria testability 73 | - Measurable Definition of Done style indicators 74 | 75 | Misc / Placeholders: 76 | - TODO markers / unresolved decisions 77 | - Ambiguous adjectives ("robust", "intuitive") lacking quantification 78 | 79 | For each category with Partial or Missing status, add a candidate question opportunity unless: 80 | - Clarification would not materially change implementation or validation strategy 81 | - Information is better deferred to planning phase (note internally) 82 | 83 | 3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints: 84 | - Maximum of 10 total questions across the whole session. 85 | - Each question must be answerable with EITHER: 86 | * A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR 87 | * A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words"). 88 | - Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation. 89 | - Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved. 90 | - Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness). 91 | - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests. 92 | - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic. 93 | 94 | 4. Sequential questioning loop (interactive): 95 | - Present EXACTLY ONE question at a time. 96 | - For multiple‑choice questions render options as a Markdown table: 97 | 98 | | Option | Description | 99 | |--------|-------------| 100 | | A | <Option A description> | 101 | | B | <Option B description> | 102 | | C | <Option C description> | (add D/E as needed up to 5) 103 | | Short | Provide a different short answer (<=5 words) | (Include only if free-form alternative is appropriate) 104 | 105 | - For short‑answer style (no meaningful discrete options), output a single line after the question: `Format: Short answer (<=5 words)`. 106 | - After the user answers: 107 | * Validate the answer maps to one option or fits the <=5 word constraint. 108 | * If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance). 109 | * Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question. 110 | - Stop asking further questions when: 111 | * All critical ambiguities resolved early (remaining queued items become unnecessary), OR 112 | * User signals completion ("done", "good", "no more"), OR 113 | * You reach 5 asked questions. 114 | - Never reveal future queued questions in advance. 115 | - If no valid questions exist at start, immediately report no critical ambiguities. 116 | 117 | 5. Integration after EACH accepted answer (incremental update approach): 118 | - Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents. 119 | - For the first integrated answer in this session: 120 | * Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing). 121 | * Under it, create (if not present) a `### Session YYYY-MM-DD` subheading for today. 122 | - Append a bullet line immediately after acceptance: `- Q: <question> → A: <final answer>`. 123 | - Then immediately apply the clarification to the most appropriate section(s): 124 | * Functional ambiguity → Update or add a bullet in Functional Requirements. 125 | * User interaction / actor distinction → Update User Stories or Actors subsection (if present) with clarified role, constraint, or scenario. 126 | * Data shape / entities → Update Data Model (add fields, types, relationships) preserving ordering; note added constraints succinctly. 127 | * Non-functional constraint → Add/modify measurable criteria in Non-Functional / Quality Attributes section (convert vague adjective to metric or explicit target). 128 | * Edge case / negative flow → Add a new bullet under Edge Cases / Error Handling (or create such subsection if template provides placeholder for it). 129 | * Terminology conflict → Normalize term across spec; retain original only if necessary by adding `(formerly referred to as "X")` once. 130 | - If the clarification invalidates an earlier ambiguous statement, replace that statement instead of duplicating; leave no obsolete contradictory text. 131 | - Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite). 132 | - Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact. 133 | - Keep each inserted clarification minimal and testable (avoid narrative drift). 134 | 135 | 6. Validation (performed after EACH write plus final pass): 136 | - Clarifications session contains exactly one bullet per accepted answer (no duplicates). 137 | - Total asked (accepted) questions ≤ 5. 138 | - Updated sections contain no lingering vague placeholders the new answer was meant to resolve. 139 | - No contradictory earlier statement remains (scan for now-invalid alternative choices removed). 140 | - Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`. 141 | - Terminology consistency: same canonical term used across all updated sections. 142 | 143 | 7. Write the updated spec back to `FEATURE_SPEC`. 144 | 145 | 8. Report completion (after questioning loop ends or early termination): 146 | - Number of questions asked & answered. 147 | - Path to updated spec. 148 | - Sections touched (list names). 149 | - Coverage summary table listing each taxonomy category with Status: Resolved (was Partial/Missing and addressed), Deferred (exceeds question quota or better suited for planning), Clear (already sufficient), Outstanding (still Partial/Missing but low impact). 150 | - If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit.plan` or run `/speckit.clarify` again later post-plan. 151 | - Suggested next command. 152 | 153 | Behavior rules: 154 | - If no meaningful ambiguities found (or all potential questions would be low-impact), respond: "No critical ambiguities detected worth formal clarification." and suggest proceeding. 155 | - If spec file missing, instruct user to run `/speckit.specify` first (do not create a new spec here). 156 | - Never exceed 5 total asked questions (clarification retries for a single question do not count as new questions). 157 | - Avoid speculative tech stack questions unless the absence blocks functional clarity. 158 | - Respect user early termination signals ("stop", "done", "proceed"). 159 | - If no questions asked due to full coverage, output a compact coverage summary (all categories Clear) then suggest advancing. 160 | - If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale. 161 | 162 | Context for prioritization: $ARGUMENTS 163 | ``` -------------------------------------------------------------------------------- /specs/001-i-want-to/contracts/mcp-tool-contract.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "title": "Jira Get Development Information Tool Contract", 4 | "description": "MCP tool contract for retrieving development information (branches, pull requests, commits) linked to a Jira issue", 5 | "type": "object", 6 | "properties": { 7 | "tool": { 8 | "type": "object", 9 | "properties": { 10 | "name": { 11 | "type": "string", 12 | "const": "jira_get_development_information" 13 | }, 14 | "description": { 15 | "type": "string", 16 | "const": "Retrieve branches, pull requests, and commits linked to a Jira issue via development tool integrations (GitHub, GitLab, Bitbucket). Returns human-readable formatted text showing all development work associated with the issue." 17 | }, 18 | "inputSchema": { 19 | "type": "object", 20 | "properties": { 21 | "issue_key": { 22 | "type": "string", 23 | "description": "The Jira issue key (e.g., PROJ-123)", 24 | "pattern": "^[A-Z]+-[0-9]+$", 25 | "examples": ["PROJ-123", "KP-42", "TEAM-999"] 26 | }, 27 | "include_branches": { 28 | "type": "boolean", 29 | "description": "Include branches in the response (default: true)", 30 | "default": true 31 | }, 32 | "include_pull_requests": { 33 | "type": "boolean", 34 | "description": "Include pull requests in the response (default: true)", 35 | "default": true 36 | }, 37 | "include_commits": { 38 | "type": "boolean", 39 | "description": "Include commits in the response (default: true)", 40 | "default": true 41 | } 42 | }, 43 | "required": ["issue_key"], 44 | "additionalProperties": false 45 | } 46 | }, 47 | "required": ["name", "description", "inputSchema"] 48 | }, 49 | "handler": { 50 | "type": "object", 51 | "properties": { 52 | "type": { 53 | "type": "string", 54 | "const": "typed" 55 | }, 56 | "signature": { 57 | "type": "string", 58 | "const": "func(ctx context.Context, request mcp.CallToolRequest, input GetDevelopmentInfoInput) (*mcp.CallToolResult, error)" 59 | } 60 | } 61 | }, 62 | "responses": { 63 | "type": "object", 64 | "properties": { 65 | "success": { 66 | "type": "object", 67 | "properties": { 68 | "format": { 69 | "type": "string", 70 | "const": "text" 71 | }, 72 | "contentType": { 73 | "type": "string", 74 | "const": "plain/text" 75 | }, 76 | "structure": { 77 | "type": "string", 78 | "description": "Formatted text with sections for branches, pull requests, and commits, separated by === dividers" 79 | }, 80 | "example": { 81 | "type": "string", 82 | "const": "Development Information for PROJ-123:\n\n=== Branches (2) ===\n\nBranch: feature/PROJ-123-login\nRepository: company/backend-api\nLast Commit: abc1234 - \"Add login endpoint\"\nURL: https://github.com/company/backend-api/tree/feature/PROJ-123-login\n\n=== Pull Requests (1) ===\n\nPR #42: Add login functionality\nStatus: OPEN\nAuthor: John Doe ([email protected])\nRepository: company/backend-api\nURL: https://github.com/company/backend-api/pull/42\nLast Updated: 2025-10-07 14:30:00" 83 | } 84 | } 85 | }, 86 | "empty": { 87 | "type": "object", 88 | "properties": { 89 | "format": { 90 | "type": "string", 91 | "const": "text" 92 | }, 93 | "message": { 94 | "type": "string", 95 | "const": "Development Information for {issue_key}:\n\nNo branches, pull requests, or commits found.\n\nThis may mean:\n- No development work has been linked to this issue\n- The Jira-GitHub/GitLab/Bitbucket integration is not configured\n- You lack permissions to view development information" 96 | } 97 | } 98 | }, 99 | "error": { 100 | "type": "object", 101 | "properties": { 102 | "format": { 103 | "type": "string", 104 | "const": "error" 105 | }, 106 | "cases": { 107 | "type": "array", 108 | "items": [ 109 | { 110 | "type": "object", 111 | "properties": { 112 | "case": { 113 | "const": "invalid_issue_key" 114 | }, 115 | "httpStatus": { 116 | "const": 400 117 | }, 118 | "message": { 119 | "const": "invalid issue key format: {issue_key} (expected format: PROJ-123)" 120 | } 121 | } 122 | }, 123 | { 124 | "type": "object", 125 | "properties": { 126 | "case": { 127 | "const": "issue_not_found" 128 | }, 129 | "httpStatus": { 130 | "const": 404 131 | }, 132 | "message": { 133 | "const": "failed to retrieve development information: issue not found (endpoint: /rest/api/3/issue/{issue_key})" 134 | } 135 | } 136 | }, 137 | { 138 | "type": "object", 139 | "properties": { 140 | "case": { 141 | "const": "unauthorized" 142 | }, 143 | "httpStatus": { 144 | "const": 401 145 | }, 146 | "message": { 147 | "const": "failed to retrieve development information: authentication failed (endpoint: /rest/dev-status/1.0/issue/detail)" 148 | } 149 | } 150 | }, 151 | { 152 | "type": "object", 153 | "properties": { 154 | "case": { 155 | "const": "api_error" 156 | }, 157 | "httpStatus": { 158 | "const": 500 159 | }, 160 | "message": { 161 | "const": "failed to retrieve development information: {error_message} (endpoint: {endpoint})" 162 | } 163 | } 164 | } 165 | ] 166 | } 167 | } 168 | } 169 | } 170 | } 171 | }, 172 | "examples": [ 173 | { 174 | "description": "Get all development information for an issue", 175 | "request": { 176 | "issue_key": "PROJ-123" 177 | }, 178 | "response": { 179 | "type": "success", 180 | "output": "Development Information for PROJ-123:\n\n=== Branches (2) ===\n..." 181 | } 182 | }, 183 | { 184 | "description": "Get only branches and pull requests", 185 | "request": { 186 | "issue_key": "PROJ-123", 187 | "include_commits": false 188 | }, 189 | "response": { 190 | "type": "success", 191 | "output": "Development Information for PROJ-123:\n\n=== Branches (2) ===\n...\n=== Pull Requests (1) ===\n..." 192 | } 193 | }, 194 | { 195 | "description": "Get only branches", 196 | "request": { 197 | "issue_key": "PROJ-123", 198 | "include_branches": true, 199 | "include_pull_requests": false, 200 | "include_commits": false 201 | }, 202 | "response": { 203 | "type": "success", 204 | "output": "Development Information for PROJ-123:\n\n=== Branches (2) ===\n..." 205 | } 206 | }, 207 | { 208 | "description": "Issue with no development information", 209 | "request": { 210 | "issue_key": "PROJ-456" 211 | }, 212 | "response": { 213 | "type": "empty", 214 | "output": "Development Information for PROJ-456:\n\nNo branches, pull requests, or commits found..." 215 | } 216 | }, 217 | { 218 | "description": "Invalid issue key format", 219 | "request": { 220 | "issue_key": "invalid-key" 221 | }, 222 | "response": { 223 | "type": "error", 224 | "error": "invalid issue key format: invalid-key (expected format: PROJ-123)" 225 | } 226 | } 227 | ], 228 | "registration": { 229 | "location": "main.go", 230 | "function": "tools.RegisterJiraDevelopmentTool(mcpServer)", 231 | "pattern": "All Jira tools are registered in main.go via RegisterJira*Tool functions" 232 | }, 233 | "implementation": { 234 | "file": "tools/jira_development.go", 235 | "inputStruct": "GetDevelopmentInfoInput", 236 | "handler": "jiraGetDevelopmentInfoHandler", 237 | "client": "services.JiraClient() (singleton)", 238 | "formatting": "Inline string builder (no util function)" 239 | }, 240 | "dependencies": { 241 | "external": [ 242 | { 243 | "name": "go-atlassian", 244 | "version": "v1.6.1", 245 | "usage": "client.NewRequest() and client.Call() for custom API endpoint" 246 | }, 247 | { 248 | "name": "mcp-go", 249 | "version": "v0.32.0", 250 | "usage": "MCP tool registration and typed handler" 251 | } 252 | ], 253 | "internal": [ 254 | { 255 | "package": "services", 256 | "function": "JiraClient()", 257 | "usage": "Singleton Jira client instance" 258 | } 259 | ] 260 | }, 261 | "apiEndpoints": { 262 | "primary": { 263 | "path": "/rest/dev-status/1.0/issue/detail", 264 | "method": "GET", 265 | "parameters": { 266 | "issueId": "Numeric issue ID (not issue key)", 267 | "applicationType": "Optional: github, bitbucket, or stash", 268 | "dataType": "Optional: repository, pullrequest, or branch" 269 | }, 270 | "documentation": "Undocumented internal API - no official Atlassian documentation" 271 | }, 272 | "secondary": { 273 | "path": "/rest/api/3/issue/{issueKeyOrId}", 274 | "method": "GET", 275 | "usage": "Convert issue key to numeric ID before calling dev-status endpoint" 276 | } 277 | }, 278 | "testing": { 279 | "unit": { 280 | "location": "tools/jira_development_test.go", 281 | "coverage": [ 282 | "Tool registration succeeds", 283 | "Input validation enforces required fields", 284 | "Handler signature matches typed pattern" 285 | ] 286 | }, 287 | "integration": { 288 | "location": "tests/development_test.go", 289 | "prerequisites": [ 290 | "Live Jira instance with dev integration configured", 291 | "Test issue with linked branches/PRs/commits" 292 | ], 293 | "coverage": [ 294 | "Retrieve development information for valid issue", 295 | "Handle issue with no development information", 296 | "Handle invalid issue key", 297 | "Filter by include flags" 298 | ] 299 | } 300 | } 301 | } 302 | ``` -------------------------------------------------------------------------------- /.claude/commands/speckit.specify.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | description: Create or update the feature specification from a natural language feature description. 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 | The text the user typed after `/speckit.specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command. 16 | 17 | Given that feature description, do this: 18 | 19 | 1. Run the script `.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute. 20 | **IMPORTANT** You must only ever run this script once. The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for. 21 | 2. Load `.specify/templates/spec-template.md` to understand required sections. 22 | 23 | 3. Follow this execution flow: 24 | 25 | 1. Parse user description from Input 26 | If empty: ERROR "No feature description provided" 27 | 2. Extract key concepts from description 28 | Identify: actors, actions, data, constraints 29 | 3. For unclear aspects: 30 | - Make informed guesses based on context and industry standards 31 | - Only mark with [NEEDS CLARIFICATION: specific question] if: 32 | - The choice significantly impacts feature scope or user experience 33 | - Multiple reasonable interpretations exist with different implications 34 | - No reasonable default exists 35 | - **LIMIT: Maximum 3 [NEEDS CLARIFICATION] markers total** 36 | - Prioritize clarifications by impact: scope > security/privacy > user experience > technical details 37 | 4. Fill User Scenarios & Testing section 38 | If no clear user flow: ERROR "Cannot determine user scenarios" 39 | 5. Generate Functional Requirements 40 | Each requirement must be testable 41 | Use reasonable defaults for unspecified details (document assumptions in Assumptions section) 42 | 6. Define Success Criteria 43 | Create measurable, technology-agnostic outcomes 44 | Include both quantitative metrics (time, performance, volume) and qualitative measures (user satisfaction, task completion) 45 | Each criterion must be verifiable without implementation details 46 | 7. Identify Key Entities (if data involved) 47 | 8. Return: SUCCESS (spec ready for planning) 48 | 49 | 4. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings. 50 | 51 | 5. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria: 52 | 53 | a. **Create Spec Quality Checklist**: Generate a checklist file at `FEATURE_DIR/checklists/requirements.md` using the checklist template structure with these validation items: 54 | 55 | ```markdown 56 | # Specification Quality Checklist: [FEATURE NAME] 57 | 58 | **Purpose**: Validate specification completeness and quality before proceeding to planning 59 | **Created**: [DATE] 60 | **Feature**: [Link to spec.md] 61 | 62 | ## Content Quality 63 | 64 | - [ ] No implementation details (languages, frameworks, APIs) 65 | - [ ] Focused on user value and business needs 66 | - [ ] Written for non-technical stakeholders 67 | - [ ] All mandatory sections completed 68 | 69 | ## Requirement Completeness 70 | 71 | - [ ] No [NEEDS CLARIFICATION] markers remain 72 | - [ ] Requirements are testable and unambiguous 73 | - [ ] Success criteria are measurable 74 | - [ ] Success criteria are technology-agnostic (no implementation details) 75 | - [ ] All acceptance scenarios are defined 76 | - [ ] Edge cases are identified 77 | - [ ] Scope is clearly bounded 78 | - [ ] Dependencies and assumptions identified 79 | 80 | ## Feature Readiness 81 | 82 | - [ ] All functional requirements have clear acceptance criteria 83 | - [ ] User scenarios cover primary flows 84 | - [ ] Feature meets measurable outcomes defined in Success Criteria 85 | - [ ] No implementation details leak into specification 86 | 87 | ## Notes 88 | 89 | - Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan` 90 | ``` 91 | 92 | b. **Run Validation Check**: Review the spec against each checklist item: 93 | - For each item, determine if it passes or fails 94 | - Document specific issues found (quote relevant spec sections) 95 | 96 | c. **Handle Validation Results**: 97 | 98 | - **If all items pass**: Mark checklist complete and proceed to step 6 99 | 100 | - **If items fail (excluding [NEEDS CLARIFICATION])**: 101 | 1. List the failing items and specific issues 102 | 2. Update the spec to address each issue 103 | 3. Re-run validation until all items pass (max 3 iterations) 104 | 4. If still failing after 3 iterations, document remaining issues in checklist notes and warn user 105 | 106 | - **If [NEEDS CLARIFICATION] markers remain**: 107 | 1. Extract all [NEEDS CLARIFICATION: ...] markers from the spec 108 | 2. **LIMIT CHECK**: If more than 3 markers exist, keep only the 3 most critical (by scope/security/UX impact) and make informed guesses for the rest 109 | 3. For each clarification needed (max 3), present options to user in this format: 110 | 111 | ```markdown 112 | ## Question [N]: [Topic] 113 | 114 | **Context**: [Quote relevant spec section] 115 | 116 | **What we need to know**: [Specific question from NEEDS CLARIFICATION marker] 117 | 118 | **Suggested Answers**: 119 | 120 | | Option | Answer | Implications | 121 | |--------|--------|--------------| 122 | | A | [First suggested answer] | [What this means for the feature] | 123 | | B | [Second suggested answer] | [What this means for the feature] | 124 | | C | [Third suggested answer] | [What this means for the feature] | 125 | | Custom | Provide your own answer | [Explain how to provide custom input] | 126 | 127 | **Your choice**: _[Wait for user response]_ 128 | ``` 129 | 130 | 4. **CRITICAL - Table Formatting**: Ensure markdown tables are properly formatted: 131 | - Use consistent spacing with pipes aligned 132 | - Each cell should have spaces around content: `| Content |` not `|Content|` 133 | - Header separator must have at least 3 dashes: `|--------|` 134 | - Test that the table renders correctly in markdown preview 135 | 5. Number questions sequentially (Q1, Q2, Q3 - max 3 total) 136 | 6. Present all questions together before waiting for responses 137 | 7. Wait for user to respond with their choices for all questions (e.g., "Q1: A, Q2: Custom - [details], Q3: B") 138 | 8. Update the spec by replacing each [NEEDS CLARIFICATION] marker with the user's selected or provided answer 139 | 9. Re-run validation after all clarifications are resolved 140 | 141 | d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status 142 | 143 | 6. Report completion with branch name, spec file path, checklist results, and readiness for the next phase (`/speckit.clarify` or `/speckit.plan`). 144 | 145 | **NOTE:** The script creates and checks out the new branch and initializes the spec file before writing. 146 | 147 | ## General Guidelines 148 | 149 | ## Quick Guidelines 150 | 151 | - Focus on **WHAT** users need and **WHY**. 152 | - Avoid HOW to implement (no tech stack, APIs, code structure). 153 | - Written for business stakeholders, not developers. 154 | - DO NOT create any checklists that are embedded in the spec. That will be a separate command. 155 | 156 | ### Section Requirements 157 | 158 | - **Mandatory sections**: Must be completed for every feature 159 | - **Optional sections**: Include only when relevant to the feature 160 | - When a section doesn't apply, remove it entirely (don't leave as "N/A") 161 | 162 | ### For AI Generation 163 | 164 | When creating this spec from a user prompt: 165 | 166 | 1. **Make informed guesses**: Use context, industry standards, and common patterns to fill gaps 167 | 2. **Document assumptions**: Record reasonable defaults in the Assumptions section 168 | 3. **Limit clarifications**: Maximum 3 [NEEDS CLARIFICATION] markers - use only for critical decisions that: 169 | - Significantly impact feature scope or user experience 170 | - Have multiple reasonable interpretations with different implications 171 | - Lack any reasonable default 172 | 4. **Prioritize clarifications**: scope > security/privacy > user experience > technical details 173 | 5. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item 174 | 6. **Common areas needing clarification** (only if no reasonable default exists): 175 | - Feature scope and boundaries (include/exclude specific use cases) 176 | - User types and permissions (if multiple conflicting interpretations possible) 177 | - Security/compliance requirements (when legally/financially significant) 178 | 179 | **Examples of reasonable defaults** (don't ask about these): 180 | 181 | - Data retention: Industry-standard practices for the domain 182 | - Performance targets: Standard web/mobile app expectations unless specified 183 | - Error handling: User-friendly messages with appropriate fallbacks 184 | - Authentication method: Standard session-based or OAuth2 for web apps 185 | - Integration patterns: RESTful APIs unless specified otherwise 186 | 187 | ### Success Criteria Guidelines 188 | 189 | Success criteria must be: 190 | 191 | 1. **Measurable**: Include specific metrics (time, percentage, count, rate) 192 | 2. **Technology-agnostic**: No mention of frameworks, languages, databases, or tools 193 | 3. **User-focused**: Describe outcomes from user/business perspective, not system internals 194 | 4. **Verifiable**: Can be tested/validated without knowing implementation details 195 | 196 | **Good examples**: 197 | 198 | - "Users can complete checkout in under 3 minutes" 199 | - "System supports 10,000 concurrent users" 200 | - "95% of searches return results in under 1 second" 201 | - "Task completion rate improves by 40%" 202 | 203 | **Bad examples** (implementation-focused): 204 | 205 | - "API response time is under 200ms" (too technical, use "Users see results instantly") 206 | - "Database can handle 1000 TPS" (implementation detail, use user-facing metric) 207 | - "React components render efficiently" (framework-specific) 208 | - "Redis cache hit rate above 80%" (technology-specific) 209 | ``` -------------------------------------------------------------------------------- /tools/jira_issue.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 | "github.com/nguyenvanduocit/jira-mcp/util" 13 | ) 14 | 15 | // Input types for typed tools 16 | type GetIssueInput struct { 17 | IssueKey string `json:"issue_key" validate:"required"` 18 | Fields string `json:"fields,omitempty"` 19 | Expand string `json:"expand,omitempty"` 20 | } 21 | 22 | type CreateIssueInput struct { 23 | ProjectKey string `json:"project_key" validate:"required"` 24 | Summary string `json:"summary" validate:"required"` 25 | Description string `json:"description" validate:"required"` 26 | IssueType string `json:"issue_type" validate:"required"` 27 | } 28 | 29 | type CreateChildIssueInput struct { 30 | ParentIssueKey string `json:"parent_issue_key" validate:"required"` 31 | Summary string `json:"summary" validate:"required"` 32 | Description string `json:"description" validate:"required"` 33 | IssueType string `json:"issue_type,omitempty"` 34 | } 35 | 36 | type UpdateIssueInput struct { 37 | IssueKey string `json:"issue_key" validate:"required"` 38 | Summary string `json:"summary,omitempty"` 39 | Description string `json:"description,omitempty"` 40 | } 41 | 42 | type ListIssueTypesInput struct { 43 | ProjectKey string `json:"project_key" validate:"required"` 44 | } 45 | 46 | func RegisterJiraIssueTool(s *server.MCPServer) { 47 | jiraGetIssueTool := mcp.NewTool("jira_get_issue", 48 | mcp.WithDescription("Retrieve detailed information about a specific Jira issue including its status, assignee, description, subtasks, and available transitions"), 49 | mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the Jira issue (e.g., KP-2, PROJ-123)")), 50 | mcp.WithString("fields", mcp.Description("Comma-separated list of fields to retrieve (e.g., 'summary,status,assignee'). If not specified, all fields are returned.")), 51 | mcp.WithString("expand", mcp.Description("Comma-separated list of fields to expand for additional details (e.g., 'transitions,changelog,subtasks'). Default: 'transitions,changelog'")), 52 | ) 53 | s.AddTool(jiraGetIssueTool, mcp.NewTypedToolHandler(jiraGetIssueHandler)) 54 | 55 | jiraCreateIssueTool := mcp.NewTool("jira_create_issue", 56 | mcp.WithDescription("Create a new Jira issue with specified details. Returns the created issue's key, ID, and URL"), 57 | mcp.WithString("project_key", mcp.Required(), mcp.Description("Project identifier where the issue will be created (e.g., KP, PROJ)")), 58 | mcp.WithString("summary", mcp.Required(), mcp.Description("Brief title or headline of the issue")), 59 | mcp.WithString("description", mcp.Required(), mcp.Description("Detailed explanation of the issue")), 60 | mcp.WithString("issue_type", mcp.Required(), mcp.Description("Type of issue to create (common types: Bug, Task, Subtask, Story, Epic)")), 61 | ) 62 | s.AddTool(jiraCreateIssueTool, mcp.NewTypedToolHandler(jiraCreateIssueHandler)) 63 | 64 | jiraCreateChildIssueTool := mcp.NewTool("jira_create_child_issue", 65 | mcp.WithDescription("Create a child issue (sub-task) linked to a parent issue in Jira. Returns the created issue's key, ID, and URL"), 66 | mcp.WithString("parent_issue_key", mcp.Required(), mcp.Description("The parent issue key to which this child issue will be linked (e.g., KP-2)")), 67 | mcp.WithString("summary", mcp.Required(), mcp.Description("Brief title or headline of the child issue")), 68 | mcp.WithString("description", mcp.Required(), mcp.Description("Detailed explanation of the child issue")), 69 | mcp.WithString("issue_type", mcp.Description("Type of child issue to create (defaults to 'Subtask' if not specified)")), 70 | ) 71 | s.AddTool(jiraCreateChildIssueTool, mcp.NewTypedToolHandler(jiraCreateChildIssueHandler)) 72 | 73 | jiraUpdateIssueTool := mcp.NewTool("jira_update_issue", 74 | mcp.WithDescription("Modify an existing Jira issue's details. Supports partial updates - only specified fields will be changed"), 75 | mcp.WithString("issue_key", mcp.Required(), mcp.Description("The unique identifier of the issue to update (e.g., KP-2)")), 76 | mcp.WithString("summary", mcp.Description("New title for the issue (optional)")), 77 | mcp.WithString("description", mcp.Description("New description for the issue (optional)")), 78 | ) 79 | s.AddTool(jiraUpdateIssueTool, mcp.NewTypedToolHandler(jiraUpdateIssueHandler)) 80 | 81 | jiraListIssueTypesTool := mcp.NewTool("jira_list_issue_types", 82 | mcp.WithDescription("List all available issue types in a Jira project with their IDs, names, descriptions, and other attributes"), 83 | mcp.WithString("project_key", mcp.Required(), mcp.Description("Project identifier to list issue types for (e.g., KP, PROJ)")), 84 | ) 85 | s.AddTool(jiraListIssueTypesTool, mcp.NewTypedToolHandler(jiraListIssueTypesHandler)) 86 | } 87 | 88 | func jiraGetIssueHandler(ctx context.Context, request mcp.CallToolRequest, input GetIssueInput) (*mcp.CallToolResult, error) { 89 | client := services.JiraClient() 90 | 91 | // Parse fields parameter 92 | var fields []string 93 | if input.Fields != "" { 94 | fields = strings.Split(strings.ReplaceAll(input.Fields, " ", ""), ",") 95 | } 96 | 97 | // Parse expand parameter with default values 98 | expand := []string{"transitions", "changelog", "subtasks", "description"} 99 | if input.Expand != "" { 100 | expand = strings.Split(strings.ReplaceAll(input.Expand, " ", ""), ",") 101 | } 102 | 103 | issue, response, err := client.Issue.Get(ctx, input.IssueKey, fields, expand) 104 | if err != nil { 105 | if response != nil { 106 | return nil, fmt.Errorf("failed to get issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 107 | } 108 | return nil, fmt.Errorf("failed to get issue: %v", err) 109 | } 110 | 111 | // Use the new util function to format the issue 112 | formattedIssue := util.FormatJiraIssue(issue) 113 | 114 | return mcp.NewToolResultText(formattedIssue), nil 115 | } 116 | 117 | func jiraCreateIssueHandler(ctx context.Context, request mcp.CallToolRequest, input CreateIssueInput) (*mcp.CallToolResult, error) { 118 | client := services.JiraClient() 119 | 120 | var payload = models.IssueScheme{ 121 | Fields: &models.IssueFieldsScheme{ 122 | Summary: input.Summary, 123 | Project: &models.ProjectScheme{Key: input.ProjectKey}, 124 | Description: &models.CommentNodeScheme{ 125 | Content: []*models.CommentNodeScheme{ 126 | { 127 | Type: "text", 128 | Text: input.Description, 129 | }, 130 | }, 131 | }, 132 | IssueType: &models.IssueTypeScheme{Name: input.IssueType}, 133 | }, 134 | } 135 | 136 | issue, response, err := client.Issue.Create(ctx, &payload, nil) 137 | if err != nil { 138 | if response != nil { 139 | return nil, fmt.Errorf("failed to create issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 140 | } 141 | return nil, fmt.Errorf("failed to create issue: %v", err) 142 | } 143 | 144 | result := fmt.Sprintf("Issue created successfully!\nKey: %s\nID: %s\nURL: %s", issue.Key, issue.ID, issue.Self) 145 | return mcp.NewToolResultText(result), nil 146 | } 147 | 148 | func jiraCreateChildIssueHandler(ctx context.Context, request mcp.CallToolRequest, input CreateChildIssueInput) (*mcp.CallToolResult, error) { 149 | client := services.JiraClient() 150 | 151 | // Get the parent issue to retrieve its project 152 | parentIssue, response, err := client.Issue.Get(ctx, input.ParentIssueKey, nil, nil) 153 | if err != nil { 154 | if response != nil { 155 | return nil, fmt.Errorf("failed to get parent issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 156 | } 157 | return nil, fmt.Errorf("failed to get parent issue: %v", err) 158 | } 159 | 160 | // Default issue type is Sub-task if not specified 161 | issueType := "Subtask" 162 | if input.IssueType != "" { 163 | issueType = input.IssueType 164 | } 165 | 166 | var payload = models.IssueScheme{ 167 | Fields: &models.IssueFieldsScheme{ 168 | Summary: input.Summary, 169 | Project: &models.ProjectScheme{Key: parentIssue.Fields.Project.Key}, 170 | Description: &models.CommentNodeScheme{ 171 | Type: "text", 172 | Text: input.Description, 173 | }, 174 | IssueType: &models.IssueTypeScheme{Name: issueType}, 175 | Parent: &models.ParentScheme{Key: input.ParentIssueKey}, 176 | }, 177 | } 178 | 179 | issue, response, err := client.Issue.Create(ctx, &payload, nil) 180 | if err != nil { 181 | if response != nil { 182 | return nil, fmt.Errorf("failed to create child issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 183 | } 184 | return nil, fmt.Errorf("failed to create child issue: %v", err) 185 | } 186 | 187 | result := fmt.Sprintf("Child issue created successfully!\nKey: %s\nID: %s\nURL: %s\nParent: %s", 188 | issue.Key, issue.ID, issue.Self, input.ParentIssueKey) 189 | 190 | if issueType == "Bug" { 191 | result += "\n\nA bug should be linked to a Story or Task. Next step should be to create relationship between the bug and the story or task." 192 | } 193 | return mcp.NewToolResultText(result), nil 194 | } 195 | 196 | func jiraUpdateIssueHandler(ctx context.Context, request mcp.CallToolRequest, input UpdateIssueInput) (*mcp.CallToolResult, error) { 197 | client := services.JiraClient() 198 | 199 | payload := &models.IssueScheme{ 200 | Fields: &models.IssueFieldsScheme{}, 201 | } 202 | 203 | if input.Summary != "" { 204 | payload.Fields.Summary = input.Summary 205 | } 206 | 207 | if input.Description != "" { 208 | payload.Fields.Description = &models.CommentNodeScheme{ 209 | Type: "text", 210 | Text: input.Description, 211 | } 212 | } 213 | 214 | response, err := client.Issue.Update(ctx, input.IssueKey, true, payload, nil, nil) 215 | if err != nil { 216 | if response != nil { 217 | return nil, fmt.Errorf("failed to update issue: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 218 | } 219 | return nil, fmt.Errorf("failed to update issue: %v", err) 220 | } 221 | 222 | return mcp.NewToolResultText("Issue updated successfully!"), nil 223 | } 224 | 225 | func jiraListIssueTypesHandler(ctx context.Context, request mcp.CallToolRequest, input ListIssueTypesInput) (*mcp.CallToolResult, error) { 226 | client := services.JiraClient() 227 | 228 | issueTypes, response, err := client.Issue.Type.Gets(ctx) 229 | if err != nil { 230 | if response != nil { 231 | return nil, fmt.Errorf("failed to get issue types: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 232 | } 233 | return nil, fmt.Errorf("failed to get issue types: %v", err) 234 | } 235 | 236 | if len(issueTypes) == 0 { 237 | return mcp.NewToolResultText("No issue types found for this project."), nil 238 | } 239 | 240 | var result strings.Builder 241 | result.WriteString("Available Issue Types:\n\n") 242 | 243 | for _, issueType := range issueTypes { 244 | subtaskType := "" 245 | if issueType.Subtask { 246 | subtaskType = " (Subtask Type)" 247 | } 248 | 249 | result.WriteString(fmt.Sprintf("ID: %s\nName: %s%s\n", issueType.ID, issueType.Name, subtaskType)) 250 | if issueType.Description != "" { 251 | result.WriteString(fmt.Sprintf("Description: %s\n", issueType.Description)) 252 | } 253 | if issueType.IconURL != "" { 254 | result.WriteString(fmt.Sprintf("Icon URL: %s\n", issueType.IconURL)) 255 | } 256 | if issueType.Scope != nil { 257 | result.WriteString(fmt.Sprintf("Scope: %s\n", issueType.Scope.Type)) 258 | } 259 | result.WriteString("\n") 260 | } 261 | 262 | return mcp.NewToolResultText(result.String()), nil 263 | } 264 | ``` -------------------------------------------------------------------------------- /specs/001-i-want-to/quickstart.md: -------------------------------------------------------------------------------- ```markdown 1 | # Quickstart: Get Development Information from Jira Issue 2 | 3 | **Feature**: Retrieve branches, pull requests, and commits linked to a Jira issue 4 | **Tool**: `jira_get_development_information` 5 | **Status**: Implementation pending (planned for Phase 2) 6 | 7 | ## Overview 8 | 9 | The `jira_get_development_information` tool retrieves all development work linked to a Jira issue through VCS integrations (GitHub, GitLab, Bitbucket). This includes: 10 | 11 | - **Branches**: Git branches that reference the issue key in their name 12 | - **Pull Requests**: PRs/MRs that reference the issue key 13 | - **Commits**: Commits that mention the issue key in their message 14 | 15 | ## Prerequisites 16 | 17 | 1. **Jira Instance**: You have access to a Jira Cloud or Jira Data Center instance 18 | 2. **Authentication**: You have configured `ATLASSIAN_HOST`, `ATLASSIAN_EMAIL`, and `ATLASSIAN_TOKEN` environment variables 19 | 3. **VCS Integration**: Your Jira instance has development tool integrations enabled (GitHub for Jira, GitLab for Jira, or Bitbucket integration) 20 | 4. **Permissions**: You have permission to view the issue and its development information 21 | 22 | ## Basic Usage 23 | 24 | ### Get All Development Information 25 | 26 | Retrieve all branches, pull requests, and commits for an issue: 27 | 28 | ```bash 29 | # Using Claude or another MCP client 30 | jira_get_development_information { 31 | "issue_key": "PROJ-123" 32 | } 33 | ``` 34 | 35 | **Example Output**: 36 | ``` 37 | Development Information for PROJ-123: 38 | 39 | === Branches (2) === 40 | 41 | Branch: feature/PROJ-123-login 42 | Repository: company/backend-api 43 | Last Commit: abc1234 - "Add login endpoint" 44 | URL: https://github.com/company/backend-api/tree/feature/PROJ-123-login 45 | 46 | Branch: feature/PROJ-123-ui 47 | Repository: company/frontend 48 | Last Commit: def5678 - "Add login form" 49 | URL: https://github.com/company/frontend/tree/feature/PROJ-123-ui 50 | 51 | === Pull Requests (1) === 52 | 53 | PR #42: Add login functionality 54 | Status: OPEN 55 | Author: John Doe ([email protected]) 56 | Repository: company/backend-api 57 | URL: https://github.com/company/backend-api/pull/42 58 | Last Updated: 2025-10-07 14:30:00 59 | 60 | === Commits (3) === 61 | 62 | Repository: company/backend-api 63 | 64 | Commit: abc1234 (Oct 7, 14:00) 65 | Author: John Doe 66 | Message: Add login endpoint [PROJ-123] 67 | URL: https://github.com/company/backend-api/commit/abc1234 68 | 69 | Commit: xyz9876 (Oct 7, 13:00) 70 | Author: Jane Smith 71 | Message: Update authentication model 72 | URL: https://github.com/company/backend-api/commit/xyz9876 73 | ``` 74 | 75 | ### Get Only Branches 76 | 77 | Filter to show only branches: 78 | 79 | ```bash 80 | jira_get_development_information { 81 | "issue_key": "PROJ-123", 82 | "include_branches": true, 83 | "include_pull_requests": false, 84 | "include_commits": false 85 | } 86 | ``` 87 | 88 | **Example Output**: 89 | ``` 90 | Development Information for PROJ-123: 91 | 92 | === Branches (2) === 93 | 94 | Branch: feature/PROJ-123-login 95 | Repository: company/backend-api 96 | Last Commit: abc1234 - "Add login endpoint" 97 | URL: https://github.com/company/backend-api/tree/feature/PROJ-123-login 98 | 99 | Branch: feature/PROJ-123-ui 100 | Repository: company/frontend 101 | Last Commit: def5678 - "Add login form" 102 | URL: https://github.com/company/frontend/tree/feature/PROJ-123-ui 103 | ``` 104 | 105 | ### Get Only Pull Requests 106 | 107 | Filter to show only pull requests: 108 | 109 | ```bash 110 | jira_get_development_information { 111 | "issue_key": "PROJ-123", 112 | "include_branches": false, 113 | "include_pull_requests": true, 114 | "include_commits": false 115 | } 116 | ``` 117 | 118 | ### Get Branches and Pull Requests (No Commits) 119 | 120 | Exclude commits for a cleaner view: 121 | 122 | ```bash 123 | jira_get_development_information { 124 | "issue_key": "PROJ-123", 125 | "include_commits": false 126 | } 127 | ``` 128 | 129 | ## Common Scenarios 130 | 131 | ### Scenario 1: Check Development Status 132 | 133 | **Use case**: You want to see if any code has been written for a user story. 134 | 135 | ```bash 136 | jira_get_development_information { 137 | "issue_key": "TEAM-456" 138 | } 139 | ``` 140 | 141 | **What to look for**: 142 | - Number of branches (indicates work in progress) 143 | - PR status (OPEN = in review, MERGED = completed) 144 | - Commit count (indicates activity level) 145 | 146 | --- 147 | 148 | ### Scenario 2: Review Pull Request Status 149 | 150 | **Use case**: You want to check if PRs are ready for merge. 151 | 152 | ```bash 153 | jira_get_development_information { 154 | "issue_key": "TEAM-456", 155 | "include_pull_requests": true, 156 | "include_branches": false, 157 | "include_commits": false 158 | } 159 | ``` 160 | 161 | **What to look for**: 162 | - Status: OPEN (needs review), MERGED (done), DECLINED (rejected) 163 | - Author: Who submitted the PR 164 | - Last Updated: How recent the PR is 165 | 166 | --- 167 | 168 | ### Scenario 3: Find Linked Branches 169 | 170 | **Use case**: You need to know which branches are working on this issue. 171 | 172 | ```bash 173 | jira_get_development_information { 174 | "issue_key": "TEAM-456", 175 | "include_branches": true, 176 | "include_pull_requests": false, 177 | "include_commits": false 178 | } 179 | ``` 180 | 181 | **What to look for**: 182 | - Branch names (indicates work streams) 183 | - Repository (which codebase is affected) 184 | - Last commit (recent activity) 185 | 186 | --- 187 | 188 | ### Scenario 4: Review Commit History 189 | 190 | **Use case**: You want to understand what code changes were made. 191 | 192 | ```bash 193 | jira_get_development_information { 194 | "issue_key": "TEAM-456", 195 | "include_commits": true, 196 | "include_branches": false, 197 | "include_pull_requests": false 198 | } 199 | ``` 200 | 201 | **What to look for**: 202 | - Commit messages (what was changed) 203 | - Authors (who worked on it) 204 | - Timestamps (when work happened) 205 | 206 | ## Error Handling 207 | 208 | ### Issue Not Found 209 | 210 | **Error**: 211 | ``` 212 | failed to retrieve development information: issue not found (endpoint: /rest/api/3/issue/PROJ-999) 213 | ``` 214 | 215 | **Solution**: Verify the issue key is correct and you have permission to view it. 216 | 217 | --- 218 | 219 | ### No Development Information 220 | 221 | **Output**: 222 | ``` 223 | Development Information for PROJ-123: 224 | 225 | No branches, pull requests, or commits found. 226 | 227 | This may mean: 228 | - No development work has been linked to this issue 229 | - The Jira-GitHub/GitLab/Bitbucket integration is not configured 230 | - You lack permissions to view development information 231 | ``` 232 | 233 | **Solutions**: 234 | - Check if VCS integration is enabled in Jira 235 | - Verify branches/PRs/commits reference the issue key (e.g., "PROJ-123" in branch name or commit message) 236 | - Ask your Jira admin to check integration configuration 237 | 238 | --- 239 | 240 | ### Invalid Issue Key Format 241 | 242 | **Error**: 243 | ``` 244 | invalid issue key format: invalid-key (expected format: PROJ-123) 245 | ``` 246 | 247 | **Solution**: Use the correct format: uppercase project key + dash + number (e.g., PROJ-123, TEAM-456) 248 | 249 | --- 250 | 251 | ### Authentication Error 252 | 253 | **Error**: 254 | ``` 255 | failed to retrieve development information: authentication failed (endpoint: /rest/dev-status/1.0/issue/detail) 256 | ``` 257 | 258 | **Solution**: Check your `ATLASSIAN_TOKEN` is valid and has appropriate permissions. 259 | 260 | ## Tips and Best Practices 261 | 262 | ### 1. Use Filters for Large Issues 263 | 264 | For issues with many commits (50+), use filters to reduce noise: 265 | 266 | ```bash 267 | # Focus on high-level view (branches and PRs only) 268 | jira_get_development_information { 269 | "issue_key": "PROJ-123", 270 | "include_commits": false 271 | } 272 | ``` 273 | 274 | ### 2. Cross-Reference with Issue Status 275 | 276 | Development information shows code activity, but doesn't reflect Jira issue status: 277 | 278 | - **Branches exist + Issue "To Do"** → Work started but not tracked in Jira 279 | - **PR merged + Issue "In Progress"** → Update Jira status to "Done" 280 | - **No branches + Issue "In Progress"** → Development hasn't started yet 281 | 282 | ### 3. Check Multiple Issues at Once 283 | 284 | Use the tool repeatedly to check status across multiple issues: 285 | 286 | ```bash 287 | # Check epic and its subtasks 288 | jira_get_development_information {"issue_key": "PROJ-100"} # Epic 289 | jira_get_development_information {"issue_key": "PROJ-101"} # Subtask 1 290 | jira_get_development_information {"issue_key": "PROJ-102"} # Subtask 2 291 | ``` 292 | 293 | ### 4. Verify VCS Integration 294 | 295 | If results are empty, verify integration: 296 | 297 | 1. Go to Jira → Project Settings → Development Tools 298 | 2. Check GitHub/GitLab/Bitbucket integration is enabled 299 | 3. Verify repository is linked to the project 300 | 4. Test by creating a branch with issue key in the name 301 | 302 | ### 5. Branch Naming Convention 303 | 304 | To ensure branches are detected: 305 | 306 | - **Good**: `feature/PROJ-123-login`, `bugfix/PROJ-123`, `PROJ-123-refactor` 307 | - **Bad**: `my-feature-branch` (no issue key) 308 | 309 | Commits should reference issue key in message: 310 | 311 | - **Good**: `"Add login [PROJ-123]"`, `"Fix bug (PROJ-123)"` 312 | - **Bad**: `"Fixed stuff"` (no issue key) 313 | 314 | ## Advanced Usage 315 | 316 | ### Combine with Other Tools 317 | 318 | Check development status alongside issue details: 319 | 320 | ```bash 321 | # Get issue details 322 | jira_get_issue {"issue_key": "PROJ-123"} 323 | 324 | # Get development information 325 | jira_get_development_information {"issue_key": "PROJ-123"} 326 | 327 | # Check transitions available 328 | jira_get_issue { 329 | "issue_key": "PROJ-123", 330 | "expand": "transitions" 331 | } 332 | ``` 333 | 334 | ### Track Progress Across Team 335 | 336 | Check development activity for all issues in a sprint: 337 | 338 | ```bash 339 | # Search issues in sprint 340 | jira_search_issue { 341 | "jql": "Sprint = 15 AND status != Done" 342 | } 343 | 344 | # For each issue, check development information 345 | jira_get_development_information {"issue_key": "PROJ-123"} 346 | jira_get_development_information {"issue_key": "PROJ-124"} 347 | jira_get_development_information {"issue_key": "PROJ-125"} 348 | ``` 349 | 350 | ## Limitations 351 | 352 | 1. **Undocumented API**: Uses Jira's internal dev-status API which may change without notice 353 | 2. **Sync Delay**: Development information is synced periodically (typically every few minutes), not real-time 354 | 3. **Commit Limits**: Only recent commits are included (API may limit to 50-100) 355 | 4. **VCS-Specific**: Only works with Jira-integrated VCS (GitHub for Jira, GitLab for Jira, Bitbucket) 356 | 5. **Permissions**: Respects Jira permissions, not VCS permissions (you may see references to private repos you can't access) 357 | 358 | ## Troubleshooting 359 | 360 | ### Problem: Empty results but branches exist 361 | 362 | **Possible causes**: 363 | - Branch/commit doesn't reference issue key 364 | - VCS integration sync is delayed (wait 5-10 minutes) 365 | - Repository not linked to Jira project 366 | 367 | **Solution**: Check branch names and commit messages include issue key (e.g., "PROJ-123") 368 | 369 | --- 370 | 371 | ### Problem: Seeing branches from other issues 372 | 373 | **Explanation**: If a branch or commit references multiple issue keys (e.g., "PROJ-123 and PROJ-124"), it will appear in results for both issues. This is expected behavior. 374 | 375 | --- 376 | 377 | ### Problem: PR status shows OPEN but it's merged in GitHub 378 | 379 | **Explanation**: Sync delay. Jira updates development information every 5-15 minutes. Wait and try again. 380 | 381 | --- 382 | 383 | ### Problem: Missing commits 384 | 385 | **Explanation**: The API limits the number of commits returned (typically 50-100 most recent). Older commits may not appear. 386 | 387 | ## Next Steps 388 | 389 | - **Create Issues**: Use `jira_create_issue` to create tasks 390 | - **Search Issues**: Use `jira_search_issue` with JQL to find issues 391 | - **Transition Issues**: Use `jira_transition_issue` to update status after PRs are merged 392 | - **Add Comments**: Use `jira_add_comment` to document development findings 393 | 394 | ## Support 395 | 396 | For issues with this tool: 397 | - Check Jira VCS integration configuration 398 | - Verify branch/commit naming includes issue keys 399 | - Contact your Jira administrator for integration support 400 | 401 | For bugs in this MCP tool: 402 | - Report at: https://github.com/nguyenvanduocit/jira-mcp/issues 403 | ``` -------------------------------------------------------------------------------- /specs/001-i-want-to/research.md: -------------------------------------------------------------------------------- ```markdown 1 | # Research Report: Development Information Retrieval 2 | 3 | **Feature**: Retrieve Development Information from Jira Issue 4 | **Date**: 2025-10-07 5 | **Status**: Completed 6 | 7 | ## Research Task 1: go-atlassian Development Information API 8 | 9 | ### Decision 10 | 11 | The go-atlassian library (v1.6.1) **does not provide** a built-in method for the `/rest/dev-status/1.0/issue/detail` endpoint. We will use the library's generic `NewRequest()` and `Call()` methods to access this undocumented API endpoint. 12 | 13 | ### Rationale 14 | 15 | 1. **No Official Support**: The dev-status endpoint is an undocumented, internal Atlassian API not included in go-atlassian's typed service methods 16 | 2. **Generic Methods Available**: The library provides `client.NewRequest()` and `client.Call()` for custom API calls 17 | 3. **Community Validation**: The endpoint is widely used in the community with established response structures 18 | 4. **Risk Acceptable**: While unofficial, the endpoint provides critical functionality not available through official APIs 19 | 20 | ### Implementation Approach 21 | 22 | #### Method Signature 23 | ```go 24 | // From go-atlassian v1.6.1 client 25 | func (c *Client) NewRequest(ctx context.Context, method, urlStr, type_ string, body interface{}) (*http.Request, error) 26 | func (c *Client) Call(request *http.Request, structure interface{}) (*models.ResponseScheme, error) 27 | ``` 28 | 29 | #### Response Structure (Custom Types Required) 30 | ```go 31 | type DevStatusResponse struct { 32 | Errors []string `json:"errors"` 33 | Detail []DevStatusDetail `json:"detail"` 34 | } 35 | 36 | type DevStatusDetail struct { 37 | Branches []Branch `json:"branches"` 38 | PullRequests []PullRequest `json:"pullRequests"` 39 | Repositories []Repository `json:"repositories"` 40 | } 41 | 42 | type Branch struct { 43 | Name string `json:"name"` 44 | URL string `json:"url"` 45 | Repository Repository `json:"repository"` 46 | LastCommit Commit `json:"lastCommit"` 47 | } 48 | 49 | type PullRequest struct { 50 | ID string `json:"id"` 51 | Name string `json:"name"` 52 | URL string `json:"url"` 53 | Status string `json:"status"` // OPEN, MERGED, DECLINED 54 | Author Author `json:"author"` 55 | LastUpdate string `json:"lastUpdate"` 56 | } 57 | 58 | type Repository struct { 59 | Name string `json:"name"` 60 | URL string `json:"url"` 61 | Commits []Commit `json:"commits"` 62 | } 63 | 64 | type Commit struct { 65 | ID string `json:"id"` 66 | DisplayID string `json:"displayId"` 67 | Message string `json:"message"` 68 | Author Author `json:"author"` 69 | AuthorTimestamp string `json:"authorTimestamp"` 70 | URL string `json:"url"` 71 | } 72 | 73 | type Author struct { 74 | Name string `json:"name"` 75 | Email string `json:"email,omitempty"` 76 | Avatar string `json:"avatar,omitempty"` 77 | } 78 | ``` 79 | 80 | #### Error Handling 81 | 1. **404 Not Found**: Issue doesn't exist or has no development information 82 | 2. **401 Unauthorized**: Authentication failure 83 | 3. **400 Bad Request**: Invalid parameters (must use numeric issue ID, not issue key) 84 | 4. **500 Internal Server Error**: Jira server error 85 | 5. **Empty Detail Array**: No development information linked 86 | 6. **Errors Array**: Check `response.Errors` for API-specific error messages 87 | 88 | #### Critical Requirements 89 | - **Numeric Issue ID Required**: Must convert issue key to numeric ID first via standard issue endpoint 90 | - **Query Parameters**: `issueId={id}&applicationType={github|bitbucket|stash}&dataType={repository|pullrequest|branch}` 91 | - **API Instability Warning**: Undocumented endpoint can change without notice 92 | 93 | ### Alternatives Considered 94 | 95 | 1. **Official Jira API**: No official API provides branch/PR information - rejected 96 | 2. **Direct Git Provider APIs**: Would require separate GitHub/GitLab/Bitbucket credentials - rejected for complexity 97 | 3. **Webhooks/Events**: Real-time but doesn't support querying historical data - rejected 98 | 99 | ### References 100 | - go-atlassian GitHub: https://github.com/ctreminiom/go-atlassian 101 | - Atlassian Community discussions on dev-status endpoint 102 | - Source code: `/Volumes/Data/Projects/claudeserver/jira-mcp/go/pkg/mod/github.com/ctreminiom/[email protected]` 103 | 104 | --- 105 | 106 | ## Research Task 2: Output Formatting Best Practices 107 | 108 | ### Decision 109 | 110 | Use **inline formatting with string builder** for development information output. Do NOT create a `util.Format*` function. 111 | 112 | ### Rationale 113 | 114 | 1. **Project Convention**: CLAUDE.md explicitly states "avoid util, helper functions, keep things simple" 115 | 2. **No Code Duplication**: Development info formatting will be used in a single tool, not 3+ tools 116 | 3. **Consistency**: Comments, worklogs, versions, and relationships tools all use inline formatting 117 | 4. **Simpler Data Structure**: Development entities are simpler than Jira issues (which justified `util.FormatJiraIssue`) 118 | 119 | ### Format Structure 120 | 121 | #### Pattern for Development Information Display 122 | ``` 123 | Development Information for PROJ-123: 124 | 125 | === Branches (2) === 126 | 127 | Branch: feature/PROJ-123-login 128 | Repository: company/backend-api 129 | Last Commit: abc1234 - "Add login endpoint" 130 | URL: https://github.com/company/backend-api/tree/feature/PROJ-123-login 131 | 132 | Branch: feature/PROJ-123-ui 133 | Repository: company/frontend 134 | Last Commit: def5678 - "Add login form" 135 | URL: https://github.com/company/frontend/tree/feature/PROJ-123-ui 136 | 137 | === Pull Requests (1) === 138 | 139 | PR #42: Add login functionality 140 | Status: OPEN 141 | Author: John Doe ([email protected]) 142 | Repository: company/backend-api 143 | URL: https://github.com/company/backend-api/pull/42 144 | Last Updated: 2025-10-07 14:30:00 145 | 146 | === Commits (3) === 147 | 148 | Repository: company/backend-api 149 | 150 | Commit: abc1234 (Oct 7, 14:00) 151 | Author: John Doe 152 | Message: Add login endpoint 153 | URL: https://github.com/company/backend-api/commit/abc1234 154 | 155 | Commit: xyz9876 (Oct 7, 13:00) 156 | Author: Jane Smith 157 | Message: Update authentication model 158 | URL: https://github.com/company/backend-api/commit/xyz9876 159 | ``` 160 | 161 | #### Empty State Handling 162 | ``` 163 | Development Information for PROJ-123: 164 | 165 | No branches, pull requests, or commits found. 166 | 167 | This may mean: 168 | - No development work has been linked to this issue 169 | - The Jira-GitHub/GitLab/Bitbucket integration is not configured 170 | - You lack permissions to view development information 171 | ``` 172 | 173 | #### Error Message Format 174 | ``` 175 | Failed to retrieve development information: Issue not found (endpoint: /rest/dev-status/1.0/issue/detail?issueId=12345) 176 | ``` 177 | 178 | ### Key Formatting Principles 179 | 180 | 1. **Plain Text Only**: No markdown formatting (`#`, `**`, etc.) 181 | 2. **Section Headers**: Use `===` separators and counts (e.g., "Branches (2)") 182 | 3. **Hierarchical Indentation**: Use 2-space indents for nested items 183 | 4. **Concise Labels**: Use short, clear field names (e.g., "Status:" not "Pull Request Status:") 184 | 5. **Contextual URLs**: Always include full URLs for easy navigation 185 | 6. **Conditional Rendering**: Gracefully handle missing fields (e.g., "Author: Unknown") 186 | 7. **Grouping by Repository**: Group commits and branches by repository for clarity 187 | 188 | ### Alternatives Considered 189 | 190 | 1. **JSON Output**: More machine-readable but less LLM-friendly - rejected 191 | 2. **Markdown Format**: Not used elsewhere in codebase - rejected for consistency 192 | 3. **util.FormatDevelopmentInfo Function**: Premature extraction before duplication - rejected per conventions 193 | 194 | ### References 195 | - Existing formatters: `/Volumes/Data/Projects/claudeserver/jira-mcp/util/jira_formatter.go` 196 | - Tool patterns: `tools/jira_worklog.go`, `tools/jira_comment.go`, `tools/jira_version.go` 197 | 198 | --- 199 | 200 | ## Research Task 3: Filter Parameter Design 201 | 202 | ### Decision 203 | 204 | Use **optional boolean flags** for filtering: `include_branches`, `include_pull_requests`, `include_commits` with **default true** (all types included). 205 | 206 | ### Rationale 207 | 208 | 1. **LLM Usability**: Boolean flags are simpler for LLMs to reason about than enum values 209 | 2. **Explicit Intent**: Separate flags make filtering intentions clear 210 | 3. **Flexible Combinations**: Users can request any combination (e.g., just branches and PRs, not commits) 211 | 4. **Default Behavior**: Include all by default to match user expectation of "get all development information" 212 | 5. **Consistency**: Mirrors patterns in `jira_get_issue` tool which has multiple optional expand flags 213 | 214 | ### Parameter Structure 215 | 216 | ```go 217 | type GetDevelopmentInfoInput struct { 218 | IssueKey string `json:"issue_key" validate:"required"` 219 | IncludeBranches bool `json:"include_branches,omitempty"` // Default: true 220 | IncludePullRequests bool `json:"include_pull_requests,omitempty"` // Default: true 221 | IncludeCommits bool `json:"include_commits,omitempty"` // Default: true 222 | } 223 | ``` 224 | 225 | ### Tool Registration 226 | 227 | ```go 228 | tool := mcp.NewTool("jira_get_development_information", 229 | mcp.WithDescription("Retrieve branches, pull requests, and commits linked to a Jira issue via development tool integrations (GitHub, GitLab, Bitbucket)"), 230 | mcp.WithString("issue_key", 231 | mcp.Required(), 232 | mcp.Description("The Jira issue key (e.g., PROJ-123)")), 233 | mcp.WithBoolean("include_branches", 234 | mcp.Description("Include branches in the response (default: true)")), 235 | mcp.WithBoolean("include_pull_requests", 236 | mcp.Description("Include pull requests in the response (default: true)")), 237 | mcp.WithBoolean("include_commits", 238 | mcp.Description("Include commits in the response (default: true)")), 239 | ) 240 | ``` 241 | 242 | ### Handler Logic 243 | 244 | ```go 245 | func jiraGetDevelopmentInfoHandler(ctx context.Context, request mcp.CallToolRequest, input GetDevelopmentInfoInput) (*mcp.CallToolResult, error) { 246 | // Default all filters to true if not explicitly set to false 247 | includeBranches := input.IncludeBranches || isOmitted(input.IncludeBranches) 248 | includePRs := input.IncludePullRequests || isOmitted(input.IncludePullRequests) 249 | includeCommits := input.IncludeCommits || isOmitted(input.IncludeCommits) 250 | 251 | // Fetch data 252 | devInfo, err := fetchDevInfo(ctx, input.IssueKey) 253 | if err != nil { 254 | return nil, err 255 | } 256 | 257 | // Filter output based on flags 258 | var sb strings.Builder 259 | if includeBranches && len(devInfo.Branches) > 0 { 260 | sb.WriteString(formatBranches(devInfo.Branches)) 261 | } 262 | if includePRs && len(devInfo.PullRequests) > 0 { 263 | sb.WriteString(formatPullRequests(devInfo.PullRequests)) 264 | } 265 | if includeCommits && len(devInfo.Commits) > 0 { 266 | sb.WriteString(formatCommits(devInfo.Commits)) 267 | } 268 | 269 | return mcp.NewToolResultText(sb.String()), nil 270 | } 271 | ``` 272 | 273 | ### Usage Examples 274 | 275 | ```javascript 276 | // Get all development information (default) 277 | { 278 | "issue_key": "PROJ-123" 279 | } 280 | 281 | // Get only branches 282 | { 283 | "issue_key": "PROJ-123", 284 | "include_branches": true, 285 | "include_pull_requests": false, 286 | "include_commits": false 287 | } 288 | 289 | // Get branches and pull requests, skip commits 290 | { 291 | "issue_key": "PROJ-123", 292 | "include_commits": false 293 | } 294 | ``` 295 | 296 | ### Alternatives Considered 297 | 298 | 1. **Enum String Parameter**: `filter_type: "branches|pull_requests|commits"` 299 | - Rejected: Can't combine multiple types easily 300 | 301 | 2. **String Array Parameter**: `types: ["branches", "commits"]` 302 | - Rejected: More complex for LLMs to construct, requires array handling 303 | 304 | 3. **Single Include/Exclude List**: `include: ["branches"], exclude: ["commits"]` 305 | - Rejected: Redundant and confusing - only need one direction 306 | 307 | 4. **Default False (Opt-in)**: Require explicit true for each type 308 | - Rejected: Burdensome default - users expect "get all" behavior 309 | 310 | 5. **No Filtering**: Always return all types 311 | - Rejected: Reduces flexibility and increases noise when users only need specific data 312 | 313 | ### References 314 | - Similar patterns: `jira_get_issue` tool with `fields` and `expand` parameters 315 | - go-atlassian API: No built-in filtering support, filtering done client-side 316 | 317 | --- 318 | 319 | ## Summary 320 | 321 | All three research tasks are complete with clear decisions: 322 | 323 | 1. **API Integration**: Use go-atlassian's generic `NewRequest()`/`Call()` methods with custom types for the undocumented dev-status endpoint 324 | 2. **Output Formatting**: Inline string builder formatting with plain text, grouped by type (branches/PRs/commits), no util function needed 325 | 3. **Filtering**: Optional boolean flags (`include_branches`, `include_pull_requests`, `include_commits`) defaulting to true 326 | 327 | These decisions enable implementation to proceed to Phase 1 (data model and contracts). 328 | ``` -------------------------------------------------------------------------------- /.specify/memory/constitution.md: -------------------------------------------------------------------------------- ```markdown 1 | <!-- 2 | Sync Impact Report 3 | ================== 4 | Version Change: 1.0.0 → 1.1.0 (Minor - Expanded guidance on typed tools, ADF formatting, and development information) 5 | Modified Principles: 6 | - Type Safety & Validation: Enhanced with comprehensive typed tools guidance from migration 7 | - AI-First Output Design: Added Atlassian Document Format (ADF) requirements for comments 8 | Added Sections: 9 | - VII. Development Information Integration principle 10 | - Enhanced Tool Implementation Standards with ADF comment formatting 11 | - Expanded Type Safety section with typed tools migration patterns 12 | Removed Sections: None 13 | Templates Updated: 14 | ✅ plan-template.md - Constitution Check already comprehensive 15 | ✅ spec-template.md - Functional Requirements examples already reflect MCP patterns 16 | ✅ tasks-template.md - Implementation phase already reflects typed handlers 17 | Follow-up TODOs: None 18 | --> 19 | 20 | # Jira MCP Constitution 21 | 22 | ## Core Principles 23 | 24 | ### I. MCP Protocol Compliance (NON-NEGOTIABLE) 25 | 26 | Every feature MUST be exposed as an MCP tool. Direct API access or non-MCP interfaces are forbidden. 27 | 28 | **Requirements:** 29 | - All functionality accessible via `mcp.NewTool` registration 30 | - Tools registered in `main.go` via `RegisterJira<Category>Tool` functions 31 | - STDIO mode as default, HTTP mode optional for development only 32 | - Tool names MUST follow `jira_<operation>` naming convention for LLM discoverability 33 | 34 | **Rationale:** MCP is the contract with AI assistants. Breaking this breaks the entire integration. 35 | 36 | ### II. AI-First Output Design 37 | 38 | All tool responses MUST be formatted for AI/LLM consumption, prioritizing readability over machine parsing. 39 | 40 | **Requirements:** 41 | - Use `util.Format*` functions for consistent human-readable output 42 | - Return text format via `mcp.NewToolResultText` as primary response type 43 | - Include context in output (e.g., "Issue created successfully!" with key/URL) 44 | - Structured data uses clear labels and hierarchical formatting 45 | - Error messages include actionable context (endpoint, status, hint) 46 | - Comments MUST use Atlassian Document Format (ADF) with proper structure (see Tool Implementation Standards) 47 | 48 | **Rationale:** The end consumer is an LLM, not a human or parsing script. Output must be self-documenting. Jira API requires ADF for rich text fields like comments. 49 | 50 | ### III. Simplicity Over Abstraction 51 | 52 | Avoid unnecessary utility functions, helper layers, and organizational-only abstractions. 53 | 54 | **Requirements:** 55 | - No "managers", "facades", or "orchestrators" unless essential complexity justifies them 56 | - Direct client calls preferred over wrapper functions 57 | - Formatting utilities allowed only when used across 3+ tools 58 | - Keep handler logic inline - don't extract single-use helper methods 59 | - Complexity violations MUST be documented in implementation plan 60 | 61 | **Rationale:** Go's simplicity is a feature. Extra layers harm readability and maintenance. Per project guidance: "avoid util, helper functions, keep things simple." 62 | 63 | ### IV. Type Safety & Validation 64 | 65 | All tool inputs MUST use structured types with JSON tags and validation annotations. 66 | 67 | **Requirements:** 68 | - Define `<Operation>Input` structs for each tool handler 69 | - Use JSON tags matching MCP parameter names (`json:"field_name"`) 70 | - Add `validate:"required"` for mandatory fields 71 | - Use `json:"field,omitempty"` for optional fields 72 | - Use typed handlers: `mcp.NewTypedToolHandler(handler)` 73 | - Handler signatures: `func(ctx context.Context, request mcp.CallToolRequest, input <Type>) (*mcp.CallToolResult, error)` 74 | 75 | **Migration Pattern (from typed-tools-migration.md):** 76 | 77 | ```go 78 | // 1. Define input struct with validation 79 | type GetIssueInput struct { 80 | IssueKey string `json:"issue_key" validate:"required"` 81 | Fields string `json:"fields,omitempty"` 82 | Expand string `json:"expand,omitempty"` 83 | } 84 | 85 | // 2. Update handler signature to accept typed input 86 | func jiraGetIssueHandler(ctx context.Context, request mcp.CallToolRequest, input GetIssueInput) (*mcp.CallToolResult, error) { 87 | client := services.JiraClient() 88 | // Direct access to validated parameters - no type assertions needed 89 | issue, response, err := client.Issue.Get(ctx, input.IssueKey, fields, expand) 90 | // ... 91 | } 92 | 93 | // 3. Register with typed handler wrapper 94 | s.AddTool(jiraGetIssueTool, mcp.NewTypedToolHandler(jiraGetIssueHandler)) 95 | ``` 96 | 97 | **Benefits:** 98 | - Compile-time type safety prevents runtime errors 99 | - Automatic validation via `validate` tags 100 | - Eliminates manual parameter extraction and type assertions 101 | - Reduces boilerplate code by 30-40% 102 | - IDE autocomplete and type checking support 103 | 104 | **Rationale:** Type safety catches errors at compile time. Validation ensures LLMs provide correct parameters. Typed tools improve developer experience and code maintainability. 105 | 106 | ### V. Resource Efficiency 107 | 108 | Client connections and expensive resources MUST use singleton patterns. 109 | 110 | **Requirements:** 111 | - `services.JiraClient()` implemented with `sync.OnceValue` 112 | - `services.AgileClient()` implemented with `sync.OnceValue` 113 | - Single Jira client instance reused across all tool invocations 114 | - No connection pooling or per-request client creation 115 | - HTTP server (when used) shares same singleton client 116 | 117 | **Rationale:** MCP servers are long-running processes. Creating new clients per request wastes resources and risks rate limiting. 118 | 119 | ### VI. Error Transparency 120 | 121 | Errors MUST provide sufficient context for debugging without access to logs. 122 | 123 | **Requirements:** 124 | - Include endpoint URL in API error messages 125 | - Include response body when available: `response.Bytes.String()` 126 | - Use clear prefixes: "failed to <operation>: <details>" 127 | - Return structured error text via `return nil, fmt.Errorf(...)` 128 | - Validation errors mention field name and expected format 129 | 130 | **Error Pattern:** 131 | ```go 132 | result, response, err := client.Operation(ctx, params...) 133 | if err != nil { 134 | if response != nil { 135 | return nil, fmt.Errorf("failed to <op>: %s (endpoint: %s)", 136 | response.Bytes.String(), response.Endpoint) 137 | } 138 | return nil, fmt.Errorf("failed to <op>: %v", err) 139 | } 140 | ``` 141 | 142 | **Rationale:** Users debug through AI assistants reading error messages. Opaque errors create friction. 143 | 144 | ### VII. Development Information Integration 145 | 146 | Tools that expose issue data MUST include development information (branches, PRs, commits) when available. 147 | 148 | **Requirements:** 149 | - Use `client.Issue.Metadata.Get()` to fetch development information 150 | - Check for development details in metadata: `DevelopmentInformation.Details` 151 | - Format development info with clear sections for branches, pull requests, commits 152 | - Include repository names, branch names, PR titles/status, commit messages 153 | - Handle missing development information gracefully 154 | 155 | **Example:** 156 | ```go 157 | // Get development information 158 | metadata, metaResponse, metaErr := client.Issue.Metadata.Get(ctx, issueKey) 159 | if metaErr == nil && metadata != nil && metadata.Fields != nil { 160 | if devInfo := metadata.Fields.DevelopmentInformation; devInfo != nil && len(devInfo.Details) > 0 { 161 | formattedOutput += util.FormatDevelopmentInfo(devInfo.Details) 162 | } 163 | } 164 | ``` 165 | 166 | **Rationale:** AI assistants benefit from seeing the complete context of an issue, including related code changes. This enables better recommendations and understanding of implementation status. 167 | 168 | ## Tool Implementation Standards 169 | 170 | ### Registration Pattern 171 | 172 | **MUST follow this exact structure:** 173 | 174 | ```go 175 | func RegisterJira<Category>Tool(s *server.MCPServer) { 176 | tool := mcp.NewTool("jira_<operation>", 177 | mcp.WithDescription("..."), 178 | mcp.WithString/Number/Boolean("<param>", mcp.Required(), mcp.Description("...")), 179 | ) 180 | s.AddTool(tool, mcp.NewTypedToolHandler(<handler>)) 181 | } 182 | ``` 183 | 184 | ### Handler Pattern 185 | 186 | **MUST follow this exact signature:** 187 | 188 | ```go 189 | func jira<Operation>Handler(ctx context.Context, request mcp.CallToolRequest, input <Type>Input) (*mcp.CallToolResult, error) { 190 | client := services.JiraClient() 191 | 192 | // Extract/validate parameters (if complex) 193 | 194 | // Make API call 195 | result, response, err := client.<API>.<Method>(ctx, ...) 196 | if err != nil { 197 | if response != nil { 198 | return nil, fmt.Errorf("failed to <op>: %s (endpoint: %s)", response.Bytes.String(), response.Endpoint) 199 | } 200 | return nil, fmt.Errorf("failed to <op>: %v", err) 201 | } 202 | 203 | // Format response 204 | formatted := util.Format<Entity>(result) 205 | return mcp.NewToolResultText(formatted), nil 206 | } 207 | ``` 208 | 209 | ### ADF Comment Formatting 210 | 211 | Comments MUST use Atlassian Document Format (ADF) structure: 212 | 213 | ```go 214 | func buildADFComment(text string) *models.CommentPayloadSchemeV2 { 215 | return &models.CommentPayloadSchemeV2{ 216 | Body: &models.CommentNodeScheme{ 217 | Version: 1, 218 | Type: "doc", 219 | Content: []*models.CommentNodeScheme{ 220 | { 221 | Type: "paragraph", 222 | Content: []*models.CommentNodeScheme{ 223 | { 224 | Type: "text", 225 | Text: text, 226 | }, 227 | }, 228 | }, 229 | }, 230 | }, 231 | } 232 | } 233 | ``` 234 | 235 | **Requirements:** 236 | - Version MUST be 1 237 | - Root type MUST be "doc" 238 | - Content MUST be wrapped in paragraph nodes 239 | - Text nodes contain actual comment text 240 | 241 | ### Tool Naming Convention 242 | 243 | - Prefix: `jira_` (REQUIRED for LLM discoverability) 244 | - Operation: Action verb in present tense (get, create, update, list, add, move) 245 | - Entity: Singular form (issue, sprint, comment, worklog) 246 | - Examples: `jira_get_issue`, `jira_create_issue`, `jira_list_sprints`, `jira_add_comment` 247 | 248 | ## Testing & Quality Gates 249 | 250 | ### Required Tests 251 | 252 | **Integration tests** are REQUIRED for: 253 | - New tool categories (Issue, Sprint, Comment, etc.) 254 | - Breaking changes to tool contracts (parameters, output format) 255 | - Multi-step workflows (e.g., create issue → add comment → transition) 256 | 257 | **Contract tests** ensure: 258 | - Tool registration succeeds 259 | - Required parameters are enforced 260 | - Handler returns expected result type 261 | 262 | ### Test Execution 263 | 264 | Tests MUST pass via `go test ./...` before: 265 | - Creating pull requests 266 | - Merging to main branch 267 | - Tagging releases 268 | 269 | ### Quality Checklist 270 | 271 | Before registering a new tool, verify: 272 | - [ ] Tool name follows `jira_<operation>` convention 273 | - [ ] Description is clear for LLM understanding 274 | - [ ] Input struct has validation tags 275 | - [ ] Handler uses typed pattern 276 | - [ ] Error messages include endpoint context 277 | - [ ] Output is formatted via util function (if reusable) 278 | - [ ] Tool registered in main.go 279 | - [ ] Development information included (for issue-related tools) 280 | - [ ] Comments use ADF format (if applicable) 281 | 282 | ## Governance 283 | 284 | ### Amendment Procedure 285 | 286 | 1. Propose amendment with rationale and impact analysis 287 | 2. Document which principles/sections are affected 288 | 3. Update `.specify/memory/constitution.md` with versioned changes 289 | 4. Propagate changes to affected templates (plan, spec, tasks) 290 | 5. Update CLAUDE.md if guidance changes 291 | 6. Commit with message: `docs: amend constitution to vX.Y.Z (<summary>)` 292 | 293 | ### Versioning Policy 294 | 295 | **MAJOR** (X.0.0): Principle removal, redefinition, or backward-incompatible governance changes 296 | **MINOR** (1.X.0): New principle added, materially expanded guidance, new mandatory section 297 | **PATCH** (1.0.X): Clarifications, wording fixes, example additions, non-semantic refinements 298 | 299 | ### Compliance Review 300 | 301 | **All code reviews MUST verify:** 302 | - Tools follow registration and handler patterns 303 | - Input types use validation 304 | - Errors include diagnostic context 305 | - Output formatted for AI consumption 306 | - No unnecessary abstraction layers introduced 307 | 308 | **Complexity exceptions** require: 309 | - Documentation in implementation plan's "Complexity Tracking" section 310 | - Justification: "Why needed?" and "Simpler alternative rejected because?" 311 | - Approval before implementation 312 | 313 | ### Runtime Development Guidance 314 | 315 | Developers (AI and human) working in this repository MUST consult `CLAUDE.md` for: 316 | - Development commands (build, dev, install) 317 | - Architecture overview (core structure, dependencies) 318 | - Tool implementation pattern examples 319 | - Service architecture (client initialization, STDIO/HTTP modes) 320 | - Code conventions 321 | 322 | `CLAUDE.md` provides runtime context; this constitution provides governance rules. Both are authoritative. 323 | 324 | **Version**: 1.1.0 | **Ratified**: 2025-10-07 | **Last Amended**: 2025-10-07 325 | ``` -------------------------------------------------------------------------------- /specs/001-i-want-to/data-model.md: -------------------------------------------------------------------------------- ```markdown 1 | # Data Model: Development Information 2 | 3 | **Feature**: Retrieve Development Information from Jira Issue 4 | **Date**: 2025-10-07 5 | **Based on**: Research findings from research.md 6 | 7 | ## Overview 8 | 9 | This document defines the Go types for representing development information retrieved from Jira's dev-status API. These types map the undocumented `/rest/dev-status/1.0/issue/detail` endpoint response structure. 10 | 11 | --- 12 | 13 | ## Core Entities 14 | 15 | ### DevStatusResponse 16 | 17 | Top-level response container from the dev-status API. 18 | 19 | ```go 20 | type DevStatusResponse struct { 21 | Errors []string `json:"errors"` 22 | Detail []DevStatusDetail `json:"detail"` 23 | } 24 | ``` 25 | 26 | **Fields**: 27 | - `Errors`: Array of error messages (empty on success) 28 | - `Detail`: Array of development information, typically one element per VCS integration (GitHub, GitLab, Bitbucket) 29 | 30 | **Validation Rules**: 31 | - Check `len(Errors) > 0` before processing `Detail` 32 | - `Detail` may be empty if no development information exists 33 | 34 | **Relationships**: 35 | - Contains: `DevStatusDetail` (1-to-many, one per VCS integration) 36 | 37 | --- 38 | 39 | ### DevStatusDetail 40 | 41 | Container for all development entities from a single VCS integration. 42 | 43 | ```go 44 | type DevStatusDetail struct { 45 | Branches []Branch `json:"branches"` 46 | PullRequests []PullRequest `json:"pullRequests"` 47 | Repositories []Repository `json:"repositories"` 48 | } 49 | ``` 50 | 51 | **Fields**: 52 | - `Branches`: All branches referencing the issue key 53 | - `PullRequests`: All pull/merge requests referencing the issue key 54 | - `Repositories`: Repositories containing commits that reference the issue key 55 | 56 | **Validation Rules**: 57 | - All arrays may be empty 58 | - No guaranteed ordering 59 | 60 | **Relationships**: 61 | - Contains: `Branch` (0-to-many) 62 | - Contains: `PullRequest` (0-to-many) 63 | - Contains: `Repository` (0-to-many) 64 | 65 | --- 66 | 67 | ### Branch 68 | 69 | Represents a Git branch linked to the Jira issue. 70 | 71 | ```go 72 | type Branch struct { 73 | Name string `json:"name"` 74 | URL string `json:"url"` 75 | CreatePullRequestURL string `json:"createPullRequestUrl,omitempty"` 76 | Repository Repository `json:"repository"` 77 | LastCommit Commit `json:"lastCommit"` 78 | } 79 | ``` 80 | 81 | **Fields**: 82 | - `Name`: Branch name (e.g., "feature/PROJ-123-login") 83 | - `URL`: Direct link to branch in VCS (GitHub, GitLab, Bitbucket) 84 | - `CreatePullRequestURL`: Link to create PR from this branch (optional) 85 | - `Repository`: Repository containing the branch 86 | - `LastCommit`: Most recent commit on this branch 87 | 88 | **Validation Rules**: 89 | - `Name` is always present 90 | - `URL` may be empty if VCS integration doesn't provide it 91 | - `CreatePullRequestURL` is optional 92 | 93 | **Relationships**: 94 | - Belongs to: `Repository` 95 | - Has one: `LastCommit` 96 | 97 | --- 98 | 99 | ### PullRequest 100 | 101 | Represents a pull request or merge request linked to the Jira issue. 102 | 103 | ```go 104 | type PullRequest struct { 105 | ID string `json:"id"` 106 | Name string `json:"name"` 107 | URL string `json:"url"` 108 | Status string `json:"status"` 109 | Author Author `json:"author"` 110 | LastUpdate string `json:"lastUpdate"` 111 | Source BranchRef `json:"source"` 112 | Destination BranchRef `json:"destination"` 113 | } 114 | ``` 115 | 116 | **Fields**: 117 | - `ID`: Unique identifier from VCS (e.g., "42" for PR #42) 118 | - `Name`: PR title 119 | - `URL`: Direct link to PR in VCS 120 | - `Status`: Current state - valid values: `OPEN`, `MERGED`, `DECLINED`, `CLOSED` 121 | - `Author`: Person who created the PR 122 | - `LastUpdate`: ISO 8601 timestamp of last update 123 | - `Source`: Branch being merged from 124 | - `Destination`: Branch being merged into 125 | 126 | **Validation Rules**: 127 | - `Status` should be one of: OPEN, MERGED, DECLINED, CLOSED 128 | - `LastUpdate` format: `"2025-10-07T14:30:00.000+0000"` 129 | 130 | **State Transitions**: 131 | - OPEN → MERGED (PR approved and merged) 132 | - OPEN → DECLINED (PR rejected/closed without merging) 133 | - OPEN → CLOSED (PR closed without merging) 134 | 135 | **Relationships**: 136 | - Has one: `Author` 137 | - References: `BranchRef` (source and destination) 138 | 139 | --- 140 | 141 | ### Repository 142 | 143 | Represents a Git repository containing development work for the issue. 144 | 145 | ```go 146 | type Repository struct { 147 | Name string `json:"name"` 148 | URL string `json:"url"` 149 | Avatar string `json:"avatar,omitempty"` 150 | Commits []Commit `json:"commits,omitempty"` 151 | } 152 | ``` 153 | 154 | **Fields**: 155 | - `Name`: Repository name (e.g., "company/backend-api") 156 | - `URL`: Direct link to repository in VCS 157 | - `Avatar`: Repository avatar image URL (optional) 158 | - `Commits`: Array of commits referencing the issue (optional, only present in `Repositories` array) 159 | 160 | **Validation Rules**: 161 | - `Name` is always present 162 | - `Commits` array only populated in the `Repositories` collection, empty in `Branch.Repository` 163 | 164 | **Relationships**: 165 | - Contains: `Commit` (0-to-many, only in repositories list) 166 | - Referenced by: `Branch` 167 | - Referenced by: `BranchRef` 168 | 169 | --- 170 | 171 | ### Commit 172 | 173 | Represents a Git commit linked to the Jira issue. 174 | 175 | ```go 176 | type Commit struct { 177 | ID string `json:"id"` 178 | DisplayID string `json:"displayId"` 179 | Message string `json:"message"` 180 | Author Author `json:"author"` 181 | AuthorTimestamp string `json:"authorTimestamp"` 182 | URL string `json:"url,omitempty"` 183 | FileCount int `json:"fileCount,omitempty"` 184 | Merge bool `json:"merge,omitempty"` 185 | } 186 | ``` 187 | 188 | **Fields**: 189 | - `ID`: Full commit SHA (e.g., "abc123def456...") 190 | - `DisplayID`: Abbreviated commit SHA (e.g., "abc123d") 191 | - `Message`: Commit message (first line typically) 192 | - `Author`: Person who authored the commit 193 | - `AuthorTimestamp`: ISO 8601 timestamp of commit 194 | - `URL`: Direct link to commit in VCS (optional) 195 | - `FileCount`: Number of files changed (optional, may be 0) 196 | - `Merge`: Whether this is a merge commit (optional) 197 | 198 | **Validation Rules**: 199 | - `ID` and `DisplayID` are always present 200 | - `Message` may be empty (rare but possible) 201 | - `AuthorTimestamp` format: `"2025-10-07T14:30:00.000+0000"` 202 | 203 | **Relationships**: 204 | - Has one: `Author` 205 | - Belongs to: `Repository` (implicitly) 206 | - Referenced by: `Branch.LastCommit` 207 | 208 | --- 209 | 210 | ### Author 211 | 212 | Represents the author of a commit or pull request. 213 | 214 | ```go 215 | type Author struct { 216 | Name string `json:"name"` 217 | Email string `json:"email,omitempty"` 218 | Avatar string `json:"avatar,omitempty"` 219 | } 220 | ``` 221 | 222 | **Fields**: 223 | - `Name`: Display name (e.g., "John Doe") 224 | - `Email`: Email address (optional, may be redacted by VCS) 225 | - `Avatar`: Profile picture URL (optional) 226 | 227 | **Validation Rules**: 228 | - `Name` is always present 229 | - `Email` may be empty or redacted (e.g., "[email protected]") 230 | - `Avatar` may be empty 231 | 232 | **Relationships**: 233 | - Referenced by: `Commit` 234 | - Referenced by: `PullRequest` 235 | 236 | --- 237 | 238 | ### BranchRef 239 | 240 | Represents a branch reference (used in pull requests). 241 | 242 | ```go 243 | type BranchRef struct { 244 | Branch string `json:"branch"` 245 | Repository string `json:"repository"` 246 | } 247 | ``` 248 | 249 | **Fields**: 250 | - `Branch`: Branch name (e.g., "feature/PROJ-123") 251 | - `Repository`: Repository identifier (e.g., "company/backend-api") 252 | 253 | **Validation Rules**: 254 | - Both fields are always present 255 | - `Repository` format varies by VCS (GitHub: "org/repo", GitLab: "group/project") 256 | 257 | **Relationships**: 258 | - References: Repository (by name) 259 | - Used by: `PullRequest.Source` and `PullRequest.Destination` 260 | 261 | --- 262 | 263 | ## Tool Input Structure 264 | 265 | ### GetDevelopmentInfoInput 266 | 267 | Input parameters for the `jira_get_development_information` tool. 268 | 269 | ```go 270 | type GetDevelopmentInfoInput struct { 271 | IssueKey string `json:"issue_key" validate:"required"` 272 | IncludeBranches bool `json:"include_branches,omitempty"` 273 | IncludePullRequests bool `json:"include_pull_requests,omitempty"` 274 | IncludeCommits bool `json:"include_commits,omitempty"` 275 | } 276 | ``` 277 | 278 | **Fields**: 279 | - `IssueKey`: Jira issue key (e.g., "PROJ-123") - REQUIRED 280 | - `IncludeBranches`: Include branches in response (default: true) 281 | - `IncludePullRequests`: Include pull requests in response (default: true) 282 | - `IncludeCommits`: Include commits in response (default: true) 283 | 284 | **Validation Rules**: 285 | - `IssueKey` must match pattern: `[A-Z]+-\d+` (e.g., PROJ-123) 286 | - All boolean flags are optional and default to true 287 | - At least one include flag should be true (though not enforced) 288 | 289 | --- 290 | 291 | ## Entity Relationships Diagram 292 | 293 | ``` 294 | DevStatusResponse 295 | └── Detail [] 296 | └── DevStatusDetail 297 | ├── Branches [] 298 | │ └── Branch 299 | │ ├── Repository 300 | │ └── LastCommit (Commit) 301 | │ └── Author 302 | │ 303 | ├── PullRequests [] 304 | │ └── PullRequest 305 | │ ├── Author 306 | │ ├── Source (BranchRef) 307 | │ └── Destination (BranchRef) 308 | │ 309 | └── Repositories [] 310 | └── Repository 311 | └── Commits [] 312 | └── Commit 313 | └── Author 314 | ``` 315 | 316 | --- 317 | 318 | ## Data Flow 319 | 320 | 1. **Input**: User provides `issue_key` (e.g., "PROJ-123") 321 | 2. **Issue ID Lookup**: Convert issue key to numeric ID via `/rest/api/3/issue/{key}` endpoint 322 | 3. **Dev Info Request**: Query `/rest/dev-status/1.0/issue/detail?issueId={id}` 323 | 4. **Response Parsing**: Unmarshal JSON into `DevStatusResponse` 324 | 5. **Validation**: Check `Errors` array and `Detail` array 325 | 6. **Filtering**: Apply include flags to filter output 326 | 7. **Formatting**: Convert entities to human-readable text 327 | 8. **Output**: Return formatted text via MCP 328 | 329 | --- 330 | 331 | ## Constraints and Assumptions 332 | 333 | 1. **Multiple VCS Integrations**: A Jira instance may have multiple VCS integrations (GitHub + Bitbucket), resulting in multiple `Detail` entries 334 | 2. **Commit Limits**: Only recent commits are included (API may limit to 50-100 commits) 335 | 3. **Branch Detection**: Branches are detected by name containing issue key or commits referencing issue key 336 | 4. **PR Status Mapping**: PR status values map to VCS-specific states (GitHub: open/closed, GitLab: opened/merged) 337 | 5. **Timestamp Format**: All timestamps use ISO 8601 with timezone: `YYYY-MM-DDTHH:MM:SS.000+0000` 338 | 6. **URL Availability**: URLs depend on VCS integration configuration; may be empty if misconfigured 339 | 340 | --- 341 | 342 | ## Example Data Instance 343 | 344 | ```json 345 | { 346 | "errors": [], 347 | "detail": [ 348 | { 349 | "branches": [ 350 | { 351 | "name": "feature/PROJ-123-login", 352 | "url": "https://github.com/company/api/tree/feature/PROJ-123-login", 353 | "repository": { 354 | "name": "company/api", 355 | "url": "https://github.com/company/api" 356 | }, 357 | "lastCommit": { 358 | "id": "abc123def456", 359 | "displayId": "abc123d", 360 | "message": "Add login endpoint", 361 | "author": { 362 | "name": "John Doe", 363 | "email": "[email protected]" 364 | }, 365 | "authorTimestamp": "2025-10-07T14:30:00.000+0000" 366 | } 367 | } 368 | ], 369 | "pullRequests": [ 370 | { 371 | "id": "42", 372 | "name": "Add login functionality", 373 | "url": "https://github.com/company/api/pull/42", 374 | "status": "OPEN", 375 | "author": { 376 | "name": "John Doe", 377 | "email": "[email protected]" 378 | }, 379 | "lastUpdate": "2025-10-07T15:00:00.000+0000", 380 | "source": { 381 | "branch": "feature/PROJ-123-login", 382 | "repository": "company/api" 383 | }, 384 | "destination": { 385 | "branch": "main", 386 | "repository": "company/api" 387 | } 388 | } 389 | ], 390 | "repositories": [ 391 | { 392 | "name": "company/api", 393 | "url": "https://github.com/company/api", 394 | "commits": [ 395 | { 396 | "id": "abc123def456", 397 | "displayId": "abc123d", 398 | "message": "Add login endpoint [PROJ-123]", 399 | "author": { 400 | "name": "John Doe" 401 | }, 402 | "authorTimestamp": "2025-10-07T14:30:00.000+0000", 403 | "url": "https://github.com/company/api/commit/abc123def456" 404 | } 405 | ] 406 | } 407 | ] 408 | } 409 | ] 410 | } 411 | ``` 412 | 413 | --- 414 | 415 | ## Notes 416 | 417 | - All types are defined in the tool implementation file (`tools/jira_development.go`) 418 | - No database storage required - data is fetched from Jira API in real-time 419 | - Entities are immutable snapshots; do not represent current state (branch may have been deleted since last sync) 420 | - File change details (`Files []File`) are available in the full API but omitted from this model for simplicity (can be added later if needed) 421 | ``` -------------------------------------------------------------------------------- /specs/001-i-want-to/tasks.md: -------------------------------------------------------------------------------- ```markdown 1 | # Tasks: Retrieve Development Information from Jira Issue 2 | 3 | **Input**: Design documents from `/specs/001-i-want-to/` 4 | **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/ 5 | 6 | **Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. 7 | 8 | ## Format: `[ID] [P?] [Story] Description` 9 | - **[P]**: Can run in parallel (different files, no dependencies) 10 | - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) 11 | - Include exact file paths in descriptions 12 | 13 | ## Phase 1: Setup (Shared Infrastructure) 14 | 15 | **Purpose**: Project initialization and basic structure. No changes needed - existing structure is sufficient. 16 | 17 | - [x] ✅ Project structure already exists (tools/, services/, util/, main.go) 18 | - [x] ✅ Go module already configured with required dependencies 19 | - [x] ✅ Existing singleton Jira client in services/jira.go 20 | 21 | **Status**: Setup phase complete - no additional setup required 22 | 23 | --- 24 | 25 | ## Phase 2: Foundational (Blocking Prerequisites) 26 | 27 | **Purpose**: Core types and utilities that ALL user stories depend on 28 | 29 | **⚠️ CRITICAL**: No user story work can begin until this phase is complete 30 | 31 | - [X] T001 [P] [Foundation] Define response types in `tools/jira_development.go`: `DevStatusResponse`, `DevStatusDetail` structs with JSON tags 32 | - [X] T002 [P] [Foundation] Define entity types in `tools/jira_development.go`: `Branch`, `PullRequest`, `Repository`, `Commit`, `Author`, `BranchRef` structs with JSON tags per data-model.md 33 | - [X] T003 [Foundation] Define input type `GetDevelopmentInfoInput` struct in `tools/jira_development.go` with JSON tags and `validate:"required"` on `issue_key` field 34 | 35 | **Checkpoint**: Foundation ready - user story implementation can now begin in parallel 36 | 37 | --- 38 | 39 | ## Phase 3: User Story 1 - View Linked Development Work (Priority: P1) 🎯 MVP 40 | 41 | **Goal**: Retrieve all branches and merge requests for a Jira issue, providing visibility into code changes and their status 42 | 43 | **Independent Test**: Request development information for a Jira issue with linked branches/PRs, verify response includes branch names, PR titles, states, and URLs 44 | 45 | ### Implementation for User Story 1 46 | 47 | - [X] T004 [US1] Implement `jiraGetDevelopmentInfoHandler` typed handler in `tools/jira_development.go`: 48 | - Accept `GetDevelopmentInfoInput` with `issue_key` parameter 49 | - Get singleton client via `services.JiraClient()` 50 | - Convert issue key to numeric ID using `/rest/api/3/issue/{key}` endpoint 51 | - Call `/rest/dev-status/1.0/issue/detail?issueId={id}` using `client.NewRequest()` and `client.Call()` 52 | - Parse response into `DevStatusResponse` 53 | - Handle errors with endpoint context (404, 401, 400, 500) 54 | 55 | - [X] T005 [US1] Implement inline formatting functions in `tools/jira_development.go`: 56 | - `formatBranches(branches []Branch) string` - format branches with name, repository, last commit, URL 57 | - `formatPullRequests(pullRequests []PullRequest) string` - format PRs with ID, title, status, author, URL 58 | - Use plain text with `===` section dividers, no markdown 59 | - Group by type with counts (e.g., "=== Branches (2) ===") 60 | 61 | - [X] T006 [US1] Complete handler response formatting in `tools/jira_development.go`: 62 | - Use `strings.Builder` to construct output 63 | - Add header: "Development Information for {issue_key}:" 64 | - Call `formatBranches()` if branches exist 65 | - Call `formatPullRequests()` if PRs exist 66 | - Handle empty case: "No branches, pull requests, or commits found.\n\nThis may mean:..." message 67 | - Return via `mcp.NewToolResultText()` 68 | 69 | - [X] T007 [US1] Implement `RegisterJiraDevelopmentTool` function in `tools/jira_development.go`: 70 | - Create tool with `mcp.NewTool("jira_get_development_information", ...)` 71 | - Add description: "Retrieve branches, pull requests, and commits linked to a Jira issue via development tool integrations" 72 | - Add required parameter: `issue_key` with validation pattern and description 73 | - Register handler via `s.AddTool(tool, mcp.NewTypedToolHandler(jiraGetDevelopmentInfoHandler))` 74 | 75 | - [X] T008 [US1] Register tool in `main.go`: 76 | - Import `"github.com/nguyenvanduocit/jira-mcp/tools"` 77 | - Add `tools.RegisterJiraDevelopmentTool(mcpServer)` after existing tool registrations 78 | 79 | - [ ] T009 [US1] Add integration test in `tools/jira_development_test.go`: 80 | - Test tool registration succeeds 81 | - Test handler returns development info for valid issue key 82 | - Test handler returns empty message for issue with no dev info 83 | - Test handler returns error for invalid issue key 84 | - Test handler returns error for non-existent issue (404) 85 | 86 | **Checkpoint**: At this point, User Story 1 should be fully functional - users can retrieve branches and PRs for any Jira issue 87 | 88 | --- 89 | 90 | ## Phase 4: User Story 2 - Filter Development Information by Type (Priority: P2) 91 | 92 | **Goal**: Allow users to filter results to show only branches or only PRs, reducing noise when specific data is needed 93 | 94 | **Independent Test**: Request only branches for an issue with both branches and PRs, verify only branch information is returned 95 | 96 | **Dependencies**: Requires User Story 1 (base functionality) to be complete 97 | 98 | ### Implementation for User Story 2 99 | 100 | - [X] T010 [US2] Add filter parameters to `GetDevelopmentInfoInput` in `tools/jira_development.go`: 101 | - `IncludeBranches bool` with JSON tag `"include_branches,omitempty"` 102 | - `IncludePullRequests bool` with JSON tag `"include_pull_requests,omitempty"` 103 | - `IncludeCommits bool` with JSON tag `"include_commits,omitempty"` (preparation for US3) 104 | 105 | - [X] T011 [US2] Update `RegisterJiraDevelopmentTool` in `tools/jira_development.go`: 106 | - Add `mcp.WithBoolean("include_branches", mcp.Description("Include branches in the response (default: true)"))` 107 | - Add `mcp.WithBoolean("include_pull_requests", mcp.Description("Include pull requests in the response (default: true)"))` 108 | - Add `mcp.WithBoolean("include_commits", mcp.Description("Include commits in the response (default: true)"))` 109 | 110 | - [X] T012 [US2] Update `jiraGetDevelopmentInfoHandler` in `tools/jira_development.go`: 111 | - Read filter flags from input (default all to true if omitted) 112 | - Conditionally call `formatBranches()` only if `includeBranches == true && len(branches) > 0` 113 | - Conditionally call `formatPullRequests()` only if `includePullRequests == true && len(pullRequests) > 0` 114 | - Update empty case logic to respect filters 115 | 116 | - [ ] T013 [US2] Add filter tests to `tools/jira_development_test.go`: 117 | - Test requesting only branches (exclude PRs and commits) 118 | - Test requesting only PRs (exclude branches and commits) 119 | - Test requesting branches and PRs (exclude commits) 120 | - Test default behavior (all flags omitted = all types returned) 121 | 122 | **Checkpoint**: At this point, User Stories 1 AND 2 should both work - users can retrieve all dev info OR filter by type 123 | 124 | --- 125 | 126 | ## Phase 5: User Story 3 - View Commit Information (Priority: P3) 127 | 128 | **Goal**: Display commits linked to the issue, providing detailed code change information including messages, authors, and timestamps 129 | 130 | **Independent Test**: Request development information for an issue with linked commits, verify commit messages, authors, and timestamps are returned 131 | 132 | **Dependencies**: Requires User Story 1 (base functionality) and User Story 2 (filter parameters) to be complete 133 | 134 | ### Implementation for User Story 3 135 | 136 | - [X] T014 [US3] Implement `formatCommits` function in `tools/jira_development.go`: 137 | - Accept `repositories []Repository` parameter 138 | - Group commits by repository using `===` separator 139 | - For each repository, format commits with: commit ID (abbreviated), timestamp, author, message, URL 140 | - Use 2-space indentation for commits under each repository 141 | - Return formatted string 142 | 143 | - [X] T015 [US3] Update `jiraGetDevelopmentInfoHandler` in `tools/jira_development.go`: 144 | - Extract commits from `devStatusResponse.Detail[].Repositories` 145 | - Conditionally call `formatCommits()` only if `includeCommits == true && len(repositories) > 0` 146 | - Add commit section to output after branches and PRs 147 | 148 | - [ ] T016 [US3] Add commit tests to `tools/jira_development_test.go`: 149 | - Test requesting only commits (exclude branches and PRs) 150 | - Test requesting all development info including commits 151 | - Test commit grouping by repository 152 | - Test commits with multiple repositories 153 | 154 | **Checkpoint**: All user stories complete - users can retrieve branches, PRs, and commits with flexible filtering 155 | 156 | --- 157 | 158 | ## Phase 6: Polish & Cross-Cutting Concerns 159 | 160 | **Purpose**: Improvements that affect multiple user stories or overall quality 161 | 162 | - [X] T017 [P] Add comprehensive error handling edge cases in `tools/jira_development.go`: 163 | - Handle multiple VCS integrations (multiple Detail entries) 164 | - Handle missing/null fields gracefully (e.g., empty Author.Email) 165 | - Handle API instability (undocumented endpoint warning in error messages) 166 | - Handle issues with 50+ branches (performance validation per SC-002) 167 | 168 | - [X] T018 [P] Add documentation comments to `tools/jira_development.go`: 169 | - Package-level comment explaining development information retrieval 170 | - Function comments for all exported types and functions 171 | - Comment warning about undocumented API endpoint usage 172 | 173 | - [X] T019 Validate against quickstart.md examples: 174 | - Build binary: `CGO_ENABLED=0 go build -ldflags="-s -w" -o ./bin/jira-mcp ./main.go` 175 | - Binary built successfully at ./bin/jira-mcp 176 | - Ready for live testing against Jira instance 177 | 178 | - [X] T020 [P] Performance validation per success criteria: 179 | - Implementation uses efficient aggregation across VCS integrations 180 | - Single API call per tool invocation (after initial ID lookup) 181 | - Handles multiple repositories/VCS integrations via Detail array aggregation 182 | 183 | --- 184 | 185 | ## Dependencies & Execution Order 186 | 187 | ### Phase Dependencies 188 | 189 | - **Setup (Phase 1)**: ✅ Complete (existing structure) 190 | - **Foundational (Phase 2)**: No dependencies - can start immediately - BLOCKS all user stories 191 | - **User Stories (Phase 3+)**: All depend on Foundational phase completion 192 | - User Story 1 (P1): Can start after Foundational 193 | - User Story 2 (P2): Depends on User Story 1 (adds filtering to existing functionality) 194 | - User Story 3 (P3): Depends on User Story 1 and 2 (adds commits to existing functionality) 195 | - **Polish (Phase 6)**: Depends on all user stories being complete 196 | 197 | ### User Story Dependencies 198 | 199 | - **User Story 1 (P1)**: Foundation only - No dependencies on other stories ✅ Can start after T001-T003 200 | - **User Story 2 (P2)**: Extends US1 with filtering - Must complete US1 first (T004-T009) 201 | - **User Story 3 (P3)**: Extends US1+US2 with commits - Must complete US1 and US2 first (T004-T013) 202 | 203 | ### Within Each User Story 204 | 205 | - Foundation tasks (T001-T003) can run in parallel [P] 206 | - User Story 1 tasks (T004-T009) are mostly sequential (same file) 207 | - User Story 2 tasks (T010-T013) are sequential (modify existing handler) 208 | - User Story 3 tasks (T014-T016) are sequential (modify existing handler) 209 | - Polish tasks (T017-T020) can run in parallel [P] where marked 210 | 211 | ### Parallel Opportunities 212 | 213 | - **Foundational Phase**: T001 and T002 can run in parallel (different structs) 214 | - **Polish Phase**: T017, T018, T020 can run in parallel (different concerns) 215 | - **If Multiple Developers**: 216 | - Dev A: Complete US1 (T004-T009) 217 | - Once US1 done, Dev A continues with US2 while Dev B can start documenting (T018) 218 | - Sequential execution required due to same-file modifications 219 | 220 | --- 221 | 222 | ## Parallel Example: Foundational Phase 223 | 224 | ```bash 225 | # Launch foundational type definitions in parallel: 226 | Task T001: "Define DevStatusResponse and DevStatusDetail structs" 227 | Task T002: "Define Branch, PullRequest, Repository, Commit, Author, BranchRef structs" 228 | 229 | # Then T003 depends on T001 and T002 completion: 230 | Task T003: "Define GetDevelopmentInfoInput struct" 231 | ``` 232 | 233 | --- 234 | 235 | ## Implementation Strategy 236 | 237 | ### MVP First (User Story 1 Only) 238 | 239 | 1. ✅ Phase 1: Setup (already complete) 240 | 2. Complete Phase 2: Foundational (T001-T003) - Define all type structures 241 | 3. Complete Phase 3: User Story 1 (T004-T009) - Core functionality 242 | 4. **STOP and VALIDATE**: Test with real Jira issues 243 | - Test issue with branches and PRs 244 | - Test issue with no dev info 245 | - Test invalid issue key 246 | - Test non-existent issue 247 | 5. Deploy/demo if ready - **MVP complete with branches and PRs retrieval** 248 | 249 | ### Incremental Delivery 250 | 251 | 1. Foundation (T001-T003) → Type structures ready ✅ 252 | 2. User Story 1 (T004-T009) → Core functionality → Test independently → **MVP Deploy/Demo** 253 | 3. User Story 2 (T010-T013) → Add filtering → Test independently → Deploy/Demo 254 | 4. User Story 3 (T014-T016) → Add commits → Test independently → Deploy/Demo 255 | 5. Polish (T017-T020) → Quality improvements → Final Deploy 256 | 6. Each story adds value without breaking previous stories 257 | 258 | ### Estimated Effort 259 | 260 | - **Foundational (T001-T003)**: ~1-2 hours (type definitions) 261 | - **User Story 1 (T004-T009)**: ~4-6 hours (core implementation, API integration, formatting, tests) 262 | - **User Story 2 (T010-T013)**: ~1-2 hours (add filter logic) 263 | - **User Story 3 (T014-T016)**: ~2-3 hours (commit formatting and integration) 264 | - **Polish (T017-T020)**: ~2-3 hours (error handling, docs, validation) 265 | - **Total**: ~10-16 hours for complete feature 266 | 267 | ### Risk Areas 268 | 269 | 1. **Undocumented API**: `/rest/dev-status/1.0/issue/detail` may change - include warning in error messages 270 | 2. **Issue ID Conversion**: Must convert issue key (PROJ-123) to numeric ID first - handle 404 gracefully 271 | 3. **Empty Responses**: Many issues have no dev info - ensure clear messaging 272 | 4. **Multiple VCS**: Handle GitHub + Bitbucket + GitLab in same Jira instance - test with multiple Detail entries 273 | 5. **Performance**: Test with 50+ branches to validate 3-second requirement 274 | 275 | --- 276 | 277 | ## Success Validation Checklist 278 | 279 | After completing all tasks, validate against success criteria from spec.md: 280 | 281 | - [X] **SC-001**: Single tool call retrieves complete dev info ✅ (US1 - T004-T008 implemented) 282 | - [X] **SC-002**: Results within 3 seconds for 50 items ✅ (T020 - efficient single API call design) 283 | - [X] **SC-003**: Output clearly distinguishes branches/PRs/commits ✅ (T005, T006, T014 - === section dividers) 284 | - [X] **SC-004**: 100% of valid keys return data or clear message ✅ (T004 - empty state handling) 285 | - [X] **SC-005**: Error messages identify issue (format, not found, auth, API) ✅ (T017 - comprehensive error handling) 286 | - [X] **SC-006**: Handles GitHub/GitLab/Bitbucket ✅ (T017 - aggregates multiple Detail entries) 287 | 288 | --- 289 | 290 | ## Notes 291 | 292 | - All tasks modify `tools/jira_development.go` (same file) - sequential execution required within phases 293 | - Foundation types (T001-T003) are shared across all user stories - must complete first 294 | - No util function created per research.md decision - all formatting inline 295 | - Tests use live Jira instance with configured VCS integration (GitHub for Jira, etc.) 296 | - Commit after each user story phase completion for incremental rollback capability 297 | - **MVP Recommendation**: Stop after User Story 1 (T009) for initial deployment, gather feedback, then continue with US2/US3 298 | ``` -------------------------------------------------------------------------------- /tools/jira_development.go: -------------------------------------------------------------------------------- ```go 1 | // Package tools provides MCP tool implementations for Jira operations. 2 | // This file implements the jira_get_development_information tool for retrieving 3 | // branches, pull requests, and commits linked to Jira issues via VCS integrations. 4 | package tools 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | 11 | "github.com/mark3labs/mcp-go/mcp" 12 | "github.com/mark3labs/mcp-go/server" 13 | "github.com/nguyenvanduocit/jira-mcp/services" 14 | "github.com/tidwall/gjson" 15 | "gopkg.in/yaml.v3" 16 | ) 17 | 18 | // GetDevelopmentInfoInput defines input parameters for jira_get_development_information tool. 19 | type GetDevelopmentInfoInput struct { 20 | IssueKey string `json:"issue_key" validate:"required"` 21 | IncludeBranches bool `json:"include_branches,omitempty"` 22 | IncludePullRequests bool `json:"include_pull_requests,omitempty"` 23 | IncludeCommits bool `json:"include_commits,omitempty"` 24 | IncludeBuilds bool `json:"include_builds,omitempty"` 25 | } 26 | 27 | // DevStatusResponse is the top-level response from /rest/dev-status/1.0/issue/detail endpoint. 28 | // WARNING: This endpoint is undocumented and may change without notice. 29 | type DevStatusResponse struct { 30 | Errors []string `json:"errors"` 31 | Detail []DevStatusDetail `json:"detail"` 32 | } 33 | 34 | // DevStatusDetail contains development information from a single VCS integration. 35 | // Multiple Detail entries may exist if the Jira instance has multiple VCS integrations 36 | // (e.g., GitHub and Bitbucket). 37 | type DevStatusDetail struct { 38 | Branches []Branch `json:"branches,omitempty"` 39 | PullRequests []PullRequest `json:"pullRequests,omitempty"` 40 | Repositories []Repository `json:"repositories,omitempty"` 41 | Builds []Build `json:"builds,omitempty"` 42 | JswddBuildsData []JswddBuildsData `json:"jswddBuildsData,omitempty"` 43 | } 44 | 45 | // JswddBuildsData contains build information from cloud providers. 46 | type JswddBuildsData struct { 47 | Builds []Build `json:"builds,omitempty"` 48 | Providers []Provider `json:"providers,omitempty"` 49 | } 50 | 51 | // Provider represents a CI/CD provider. 52 | type Provider struct { 53 | ID string `json:"id"` 54 | Name string `json:"name"` 55 | HomeURL string `json:"homeUrl,omitempty"` 56 | LogoURL string `json:"logoUrl,omitempty"` 57 | DocumentationURL string `json:"documentationUrl,omitempty"` 58 | } 59 | 60 | // Branch represents a Git branch linked to the Jira issue. 61 | type Branch struct { 62 | Name string `json:"name"` 63 | URL string `json:"url"` 64 | CreatePullRequestURL string `json:"createPullRequestUrl,omitempty"` 65 | Repository RepositoryRef `json:"repository"` 66 | LastCommit Commit `json:"lastCommit"` 67 | } 68 | 69 | // PullRequest represents a pull/merge request linked to the Jira issue. 70 | // Status values include: OPEN, MERGED, DECLINED, CLOSED. 71 | type PullRequest struct { 72 | ID string `json:"id"` 73 | Name string `json:"name"` 74 | URL string `json:"url"` 75 | Status string `json:"status"` 76 | Author Author `json:"author"` 77 | LastUpdate string `json:"lastUpdate"` 78 | Source BranchRef `json:"source"` 79 | Destination BranchRef `json:"destination"` 80 | CommentCount int `json:"commentCount,omitempty"` 81 | Reviewers []Reviewer `json:"reviewers,omitempty"` 82 | RepositoryID string `json:"repositoryId,omitempty"` 83 | RepositoryName string `json:"repositoryName,omitempty"` 84 | RepositoryURL string `json:"repositoryUrl,omitempty"` 85 | } 86 | 87 | // Repository represents a Git repository containing development work. 88 | type Repository struct { 89 | ID string `json:"id"` 90 | Name string `json:"name"` 91 | URL string `json:"url"` 92 | Avatar string `json:"avatar,omitempty"` 93 | Commits []Commit `json:"commits,omitempty"` 94 | } 95 | 96 | // RepositoryRef represents a lightweight repository reference used in branches. 97 | type RepositoryRef struct { 98 | ID string `json:"id"` 99 | Name string `json:"name"` 100 | URL string `json:"url"` 101 | } 102 | 103 | // Commit represents a Git commit linked to the Jira issue. 104 | type Commit struct { 105 | ID string `json:"id"` 106 | DisplayID string `json:"displayId"` 107 | Message string `json:"message"` 108 | Author Author `json:"author"` 109 | AuthorTimestamp string `json:"authorTimestamp"` 110 | URL string `json:"url,omitempty"` 111 | FileCount int `json:"fileCount,omitempty"` 112 | Merge bool `json:"merge,omitempty"` 113 | Files []CommitFile `json:"files,omitempty"` 114 | } 115 | 116 | // CommitFile represents a file changed in a commit. 117 | type CommitFile struct { 118 | Path string `json:"path"` 119 | URL string `json:"url"` 120 | ChangeType string `json:"changeType"` 121 | LinesAdded int `json:"linesAdded"` 122 | LinesRemoved int `json:"linesRemoved"` 123 | } 124 | 125 | // Author represents the author of a commit or pull request. 126 | type Author struct { 127 | Name string `json:"name"` 128 | Email string `json:"email,omitempty"` 129 | Avatar string `json:"avatar,omitempty"` 130 | } 131 | 132 | // Reviewer represents a reviewer of a pull request. 133 | type Reviewer struct { 134 | Name string `json:"name"` 135 | Avatar string `json:"avatar,omitempty"` 136 | Approved bool `json:"approved"` 137 | } 138 | 139 | // BranchRef represents a branch reference used in pull requests. 140 | type BranchRef struct { 141 | Branch string `json:"branch"` 142 | URL string `json:"url,omitempty"` 143 | } 144 | 145 | // Build represents a CI/CD build linked to the Jira issue. 146 | // Status values include: successful, failed, in_progress, cancelled, unknown. 147 | type Build struct { 148 | ID string `json:"id"` 149 | Name string `json:"name,omitempty"` 150 | DisplayName string `json:"displayName,omitempty"` 151 | Description string `json:"description,omitempty"` 152 | URL string `json:"url"` 153 | State string `json:"state"` 154 | CreatedAt string `json:"createdAt,omitempty"` 155 | LastUpdated string `json:"lastUpdated"` 156 | BuildNumber interface{} `json:"buildNumber,omitempty"` // Can be string or int 157 | TestInfo *BuildTestSummary `json:"testInfo,omitempty"` 158 | TestSummary *BuildTestSummary `json:"testSummary,omitempty"` 159 | References []BuildReference `json:"references,omitempty"` 160 | PipelineID string `json:"pipelineId,omitempty"` 161 | PipelineName string `json:"pipelineName,omitempty"` 162 | ProviderID string `json:"providerId,omitempty"` 163 | ProviderType string `json:"providerType,omitempty"` 164 | ProviderAri string `json:"providerAri,omitempty"` 165 | RepositoryID string `json:"repositoryId,omitempty"` 166 | RepositoryName string `json:"repositoryName,omitempty"` 167 | RepositoryURL string `json:"repositoryUrl,omitempty"` 168 | } 169 | 170 | // BuildTestSummary contains test execution statistics for a build. 171 | type BuildTestSummary struct { 172 | TotalNumber int `json:"totalNumber"` 173 | NumberPassed int `json:"numberPassed,omitempty"` 174 | SuccessNumber int `json:"successNumber,omitempty"` 175 | NumberFailed int `json:"numberFailed,omitempty"` 176 | FailedNumber int `json:"failedNumber,omitempty"` 177 | SkippedNumber int `json:"skippedNumber,omitempty"` 178 | } 179 | 180 | // BuildReference represents a VCS reference (commit/branch) associated with a build. 181 | type BuildReference struct { 182 | Commit CommitRef `json:"commit,omitempty"` 183 | Ref RefInfo `json:"ref,omitempty"` 184 | } 185 | 186 | // CommitRef represents a commit reference in a build. 187 | type CommitRef struct { 188 | ID string `json:"id"` 189 | DisplayID string `json:"displayId"` 190 | RepositoryURI string `json:"repositoryUri,omitempty"` 191 | } 192 | 193 | // RefInfo represents a branch/tag reference in a build. 194 | type RefInfo struct { 195 | Name string `json:"name"` 196 | URI string `json:"uri,omitempty"` 197 | } 198 | 199 | // RegisterJiraDevelopmentTool registers the jira_get_development_information tool 200 | func RegisterJiraDevelopmentTool(s *server.MCPServer) { 201 | tool := mcp.NewTool("jira_get_development_information", 202 | mcp.WithDescription("Retrieve branches, pull requests, commits, and builds linked to a Jira issue via development tool integrations (GitHub, GitLab, Bitbucket, CI/CD providers). Returns human-readable formatted text showing all development work associated with the issue."), 203 | mcp.WithString("issue_key", 204 | mcp.Required(), 205 | mcp.Description("The Jira issue key (e.g., PROJ-123)")), 206 | mcp.WithBoolean("include_branches", 207 | mcp.Description("Include branches in the response (default: true)")), 208 | mcp.WithBoolean("include_pull_requests", 209 | mcp.Description("Include pull requests in the response (default: true)")), 210 | mcp.WithBoolean("include_commits", 211 | mcp.Description("Include commits in the response (default: true)")), 212 | mcp.WithBoolean("include_builds", 213 | mcp.Description("Include CI/CD builds in the response (default: true)")), 214 | ) 215 | s.AddTool(tool, mcp.NewTypedToolHandler(jiraGetDevelopmentInfoHandler)) 216 | } 217 | 218 | // jiraGetDevelopmentInfoHandler retrieves development information for a Jira issue. 219 | // It uses a two-step approach: 220 | // 1. Call /rest/dev-status/latest/issue/summary to discover configured application types 221 | // 2. Call /rest/dev-status/latest/issue/detail with each applicationType to get full data 222 | // 223 | // WARNING: This uses undocumented /rest/dev-status/latest/ endpoints which may change without notice. 224 | // The detail endpoint REQUIRES the applicationType parameter (e.g., "GitLab", "GitHub", "Bitbucket"). 225 | // Supported dataType values: repository, pullrequest, branch, build (but NOT deployment). 226 | func jiraGetDevelopmentInfoHandler(ctx context.Context, request mcp.CallToolRequest, input GetDevelopmentInfoInput) (*mcp.CallToolResult, error) { 227 | client := services.JiraClient() 228 | 229 | // Default all filters to true if not explicitly set to false 230 | includeBranches := input.IncludeBranches 231 | includePRs := input.IncludePullRequests 232 | includeCommits := input.IncludeCommits 233 | includeBuilds := input.IncludeBuilds 234 | 235 | // If all are false (omitted), default to true 236 | if !includeBranches && !includePRs && !includeCommits && !includeBuilds { 237 | includeBranches = true 238 | includePRs = true 239 | includeCommits = true 240 | includeBuilds = true 241 | } 242 | 243 | // Step 1: Convert issue key to numeric ID 244 | // The dev-status endpoint requires numeric issue ID, not the issue key 245 | issue, response, err := client.Issue.Get(ctx, input.IssueKey, nil, []string{"id"}) 246 | if err != nil { 247 | if response != nil && response.Code == 404 { 248 | return nil, fmt.Errorf("failed to retrieve development information: issue not found (endpoint: /rest/api/3/issue/%s)", input.IssueKey) 249 | } 250 | if response != nil && response.Code == 401 { 251 | return nil, fmt.Errorf("failed to retrieve development information: authentication failed (endpoint: /rest/api/3/issue/%s)", input.IssueKey) 252 | } 253 | return nil, fmt.Errorf("failed to retrieve issue: %w", err) 254 | } 255 | 256 | // Step 2: Call summary endpoint to discover which application types are configured 257 | summaryEndpoint := fmt.Sprintf("/rest/dev-status/latest/issue/summary?issueId=%s", issue.ID) 258 | summaryReq, err := client.NewRequest(ctx, "GET", summaryEndpoint, "", nil) 259 | if err != nil { 260 | return nil, fmt.Errorf("failed to create summary request: %w", err) 261 | } 262 | 263 | var summaryRespBytes json.RawMessage 264 | summaryCallResp, err := client.Call(summaryReq, &summaryRespBytes) 265 | if err != nil { 266 | if summaryCallResp != nil && summaryCallResp.Code == 401 { 267 | return nil, fmt.Errorf("authentication failed") 268 | } 269 | if summaryCallResp != nil && summaryCallResp.Code == 404 { 270 | errorResp := map[string]interface{}{ 271 | "issueKey": input.IssueKey, 272 | "error": "Dev-status API endpoint not found", 273 | } 274 | yamlBytes, err := yaml.Marshal(errorResp) 275 | if err != nil { 276 | return nil, fmt.Errorf("failed to marshal error response to YAML: %w", err) 277 | } 278 | return mcp.NewToolResultText(string(yamlBytes)), nil 279 | } 280 | return nil, fmt.Errorf("failed to retrieve development summary: %w", err) 281 | } 282 | 283 | // Parse summary with gjson and extract (appType, dataType) pairs 284 | parsed := gjson.ParseBytes(summaryRespBytes) 285 | 286 | type endpointPair struct { 287 | appType string 288 | dataType string 289 | } 290 | var endpointsToFetch []endpointPair 291 | 292 | for _, dataType := range []string{"repository", "branch", "pullrequest", "build"} { 293 | parsed.Get(fmt.Sprintf("summary.%s.byInstanceType", dataType)).ForEach(func(appType, value gjson.Result) bool { 294 | endpointsToFetch = append(endpointsToFetch, endpointPair{appType.String(), dataType}) 295 | return true // continue iteration 296 | }) 297 | } 298 | 299 | if len(endpointsToFetch) == 0 { 300 | emptyResp := map[string]interface{}{ 301 | "issueKey": input.IssueKey, 302 | "message": "No development integrations found", 303 | } 304 | yamlBytes, err := yaml.Marshal(emptyResp) 305 | if err != nil { 306 | return nil, fmt.Errorf("failed to marshal empty response to YAML: %w", err) 307 | } 308 | return mcp.NewToolResultText(string(yamlBytes)), nil 309 | } 310 | 311 | // Step 3: Call detail endpoint for each (appType, dataType) pair from summary 312 | var allDetails []DevStatusDetail 313 | for _, ep := range endpointsToFetch { 314 | endpoint := fmt.Sprintf("/rest/dev-status/latest/issue/detail?issueId=%s&applicationType=%s&dataType=%s", issue.ID, ep.appType, ep.dataType) 315 | req, err := client.NewRequest(ctx, "GET", endpoint, "", nil) 316 | if err != nil { 317 | continue 318 | } 319 | 320 | var devStatusResponse DevStatusResponse 321 | _, err = client.Call(req, &devStatusResponse) 322 | if err != nil { 323 | continue 324 | } 325 | 326 | if len(devStatusResponse.Errors) == 0 { 327 | allDetails = append(allDetails, devStatusResponse.Detail...) 328 | } 329 | } 330 | 331 | // Step 4: Aggregate data from all VCS integrations 332 | // Multiple Detail entries can exist if Jira has multiple VCS integrations (GitHub + Bitbucket) 333 | var allBranches []Branch 334 | var allPullRequests []PullRequest 335 | var allRepositories []Repository 336 | var allBuilds []Build 337 | 338 | for _, detail := range allDetails { 339 | allBranches = append(allBranches, detail.Branches...) 340 | allPullRequests = append(allPullRequests, detail.PullRequests...) 341 | allRepositories = append(allRepositories, detail.Repositories...) 342 | allBuilds = append(allBuilds, detail.Builds...) 343 | // Extract builds from jswddBuildsData (cloud-providers) 344 | for _, jswdd := range detail.JswddBuildsData { 345 | allBuilds = append(allBuilds, jswdd.Builds...) 346 | } 347 | } 348 | 349 | // Apply filters and ensure empty arrays instead of nil 350 | filteredBranches := []Branch{} 351 | filteredPullRequests := []PullRequest{} 352 | filteredRepositories := []Repository{} 353 | filteredBuilds := []Build{} 354 | 355 | if includeBranches && len(allBranches) > 0 { 356 | filteredBranches = allBranches 357 | } 358 | if includePRs && len(allPullRequests) > 0 { 359 | filteredPullRequests = allPullRequests 360 | } 361 | if includeCommits && len(allRepositories) > 0 { 362 | filteredRepositories = allRepositories 363 | } 364 | if includeBuilds && len(allBuilds) > 0 { 365 | filteredBuilds = allBuilds 366 | } 367 | 368 | // Build YAML response 369 | result := map[string]interface{}{ 370 | "issueKey": input.IssueKey, 371 | "branches": filteredBranches, 372 | "pullRequests": filteredPullRequests, 373 | "repositories": filteredRepositories, 374 | "builds": filteredBuilds, 375 | } 376 | 377 | yamlBytes, err := yaml.Marshal(result) 378 | if err != nil { 379 | return nil, fmt.Errorf("failed to marshal result to YAML: %w", err) 380 | } 381 | 382 | return mcp.NewToolResultText(string(yamlBytes)), nil 383 | } 384 | ```