Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ Usage:
jira configure <host> - Configure JIRA host and token (reads token from stdin)
jira create-issue <project> <issue-type> <title> <description> [assignee] - Create a new JIRA issue
jira get-issue <issue-key> - Get details of the specified JIRA issue
jira list-issues - List issues assigned to the current user
jira update-issue-status <issue-key> <status> - Update the status of the specified JIRA issue
jira list-issues [-a=user] [-t=type] [-p=key] - List issues with optional filters
jira update-issue-status <issue-key> <status> [-f field=value]... - Update the status of the specified JIRA issue
jira get-comments <issue-key> - Get comments of the specified JIRA issue
jira add-comment <issue-key> <comment> - Add a comment to the specified JIRA issue
jira attach-file <issue-key> <file-path> - Attach a file to the specified JIRA issue
Expand All @@ -100,13 +100,28 @@ jira get-issue PROJ-123

**List your current issues:**
```bash
# List issues assigned to you (default)
jira list-issues

# Filter by project
jira list-issues -p=PROJ

# Filter by issue type
jira list-issues -t=Story

# Filter by assignee (use 'me' for current user)
jira list-issues -a=me
jira list-issues -a=john.doe

# Combine multiple filters
jira list-issues -p=PROJ -t=Bug -a=me

# Output:
# Found 3 issue(s) in the last 14 days:
# Found 3 issue(s):
#
# PROJ-123 In Progress Implement new feature
# PROJ-124 To Do Fix critical bug
# PROJ-125 In Review Update documentation
# PROJ-123 Story In Progress John Doe Implement new feature
# PROJ-124 Bug To Do Jane Smith Fix critical bug
# PROJ-125 Task In Review John Doe Update documentation
```

**Create a new issue:**
Expand All @@ -126,8 +141,16 @@ jira create-issue PROJ Task "Update documentation" "Add API documentation for ne

**Update issue status:**
```bash
# Basic status update
jira update-issue-status PROJ-123 "In Progress"
# Note: Status names must match your Jira workflow (e.g., "To Do", "In Progress", "Done")

# Update status with custom fields (e.g., effort estimate)
jira update-issue-status PROJ-123 "In Progress" -f "Effort Estimate"=0

# Multiple custom fields
jira update-issue-status PROJ-123 "In Progress" -f "Effort Estimate"=0 -f "Story Points"=5
# Note: Field names must match the exact field names in your Jira instance
```

**Add a comment:**
Expand Down Expand Up @@ -190,11 +213,11 @@ Learn more about MCP: https://modelcontextprotocol.io

The server exposes the following tools:
- `get_issue` - Get details of a JIRA issue (e.g., status, summary, reporter, description)
- `update_issue_status` - Update the status of a JIRA issue using transitions
- `update_issue_status` - Update the status of a JIRA issue using transitions. Supports custom fields via the `-f` flag
- `add_comment` - Add a comment to a JIRA issue
- `get_comments` - Get all comments on a JIRA issue
- `create_issue` - Create a new JIRA issue with specified project, issue type (Story/Bug/Task), title, description, and optional assignee
- `list_issues` - List issues assigned to the current user that are unresolved and updated in the last 14 days
- `list_issues` - List issues with optional filters (assignee, issue_type, project). Defaults to current user's unresolved issues updated in the last 14 days
- `attach_file` - Attach a file to a JIRA issue
- `assign_issue` - Assign a JIRA issue to a user
- `add_issue_to_sprint` - Add a JIRA issue to the current active sprint
Expand Down
22 changes: 22 additions & 0 deletions internal/flag/field_flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package flag

import (
"fmt"
"strings"
)

// FieldFlag is a custom flag type that accumulates multiple field=value pairs
type FieldFlag map[string]string

func (f FieldFlag) String() string {
return ""
}

func (f FieldFlag) Set(value string) error {
parts := strings.SplitN(value, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid field format: %s (expected field=value)", value)
}
f[parts[0]] = parts[1]
return nil
}
112 changes: 112 additions & 0 deletions internal/flag/field_flag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package flag

import (
"testing"
)

func TestFieldFlag_String(t *testing.T) {
f := make(FieldFlag)
if f.String() != "" {
t.Errorf("String() should return empty string, got %q", f.String())
}
}

func TestFieldFlag_Set(t *testing.T) {
tests := []struct {
name string
value string
wantKey string
wantValue string
wantErr bool
}{
{
name: "valid field=value",
value: "Effort Estimate=0",
wantKey: "Effort Estimate",
wantValue: "0",
wantErr: false,
},
{
name: "valid with spaces",
value: "Story Points=5",
wantKey: "Story Points",
wantValue: "5",
wantErr: false,
},
{
name: "valid with equals in value",
value: "field=value=with=equals",
wantKey: "field",
wantValue: "value=with=equals",
wantErr: false,
},
{
name: "missing equals",
value: "noequals",
wantErr: true,
},
{
name: "empty value",
value: "field=",
wantKey: "field",
wantValue: "",
wantErr: false,
},
{
name: "empty key",
value: "=value",
wantKey: "",
wantValue: "value",
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := make(FieldFlag)
err := f.Set(tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr {
if gotValue, ok := f[tt.wantKey]; !ok {
t.Errorf("Set() key %q not found in map", tt.wantKey)
} else if gotValue != tt.wantValue {
t.Errorf("Set() value = %q, want %q", gotValue, tt.wantValue)
}
}
})
}
}

func TestFieldFlag_Set_Multiple(t *testing.T) {
f := make(FieldFlag)

values := []string{
"Effort Estimate=0",
"Story Points=5",
"Priority=High",
}

for _, v := range values {
if err := f.Set(v); err != nil {
t.Errorf("Set(%q) error = %v", v, err)
}
}

if len(f) != len(values) {
t.Errorf("Expected %d entries, got %d", len(values), len(f))
}

if f["Effort Estimate"] != "0" {
t.Errorf("Effort Estimate = %q, want %q", f["Effort Estimate"], "0")
}
if f["Story Points"] != "5" {
t.Errorf("Story Points = %q, want %q", f["Story Points"], "5")
}
if f["Priority"] != "High" {
t.Errorf("Priority = %q, want %q", f["Priority"], "High")
}
}

Loading