Architecture: Jira Entity Relationships SOP
Date: 2025-12-15
Entity Hierarchy
┌─────────────────────────────────────────────────────────────────────────┐
│ JIRA PROJECT │
│ (e.g., "PROJ", "SCRUM") │
│ │
│ project_key: "PROJ" │
│ project_id: 10001 │
│ Contains: Issues, Versions, Components │
└───────────────────────────────┬─────────────────────────────────────────┘
│
│ A project can have MULTIPLE boards
│ A board is filtered by project_key
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BOARD │
│ (Scrum Board or Kanban Board) │
│ │
│ board_id: 1 │
│ board_type: "scrum" | "kanban" | "simple" │
│ filter_id: 10100 (JQL filter that defines board scope) │
│ location: { type: "project", projectKeyOrId: "PROJ" } │
│ │
│ SCRUM boards have: Sprints, Backlog │
│ KANBAN boards have: Columns only (no sprints) │
└───────────────────────────────┬─────────────────────────────────────────┘
│
│ Only SCRUM boards have sprints
│ Sprints are created ON a board (origin_board_id)
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SPRINT │
│ (Only for Scrum Boards) │
│ │
│ sprint_id: 37 │
│ origin_board_id: 1 (board where sprint was created) │
│ state: "future" | "active" | "closed" │
│ start_date: "2025-01-01T00:00:00.000Z" │
│ end_date: "2025-01-14T00:00:00.000Z" │
│ goal: "Complete user authentication" │
│ │
│ Contains: Issues assigned to this sprint │
└───────────────────────────────┬─────────────────────────────────────────┘
│
│ Issues are assigned TO a sprint
│ Issues belong to a PROJECT, displayed on BOARD
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ ISSUE │
│ (Story, Task, Bug, Epic, etc.) │
│ │
│ issue_key: "PROJ-123" │
│ project_key: "PROJ" (from issue_key prefix) │
│ sprint: [37] (can be in ONE active sprint) │
│ status: "To Do" | "In Progress" | "Done" │
│ │
│ Issues NOT in a sprint are in the BACKLOG │
└─────────────────────────────────────────────────────────────────────────┘
Key Relationships
1. Project → Board (One-to-Many)
- A Project can have multiple Boards
- A Board is typically associated with one Project (via filter)
- Query:
get_all_boards(project_key_or_id="PROJ")
2. Board → Sprint (One-to-Many, Scrum only)
- A Scrum Board can have multiple Sprints
- A Sprint belongs to one Board (
origin_board_id) - Query:
get_board_sprints(board_id=1)
3. Sprint → Issues (One-to-Many)
- A Sprint contains multiple Issues
- An Issue can be in one active Sprint at a time
- Query:
get_sprint_issues(sprint_id=37)
4. Board → Issues (Filtered view)
- A Board displays Issues matching its filter
- Issues are not "owned" by a board, just displayed on it
- Query:
get_board_issues(board_id=1)
5. Backlog (Special case)
- Issues not assigned to any sprint are in the Backlog
- Backlog is accessed via the Board
- Query:
get_backlog_issues(board_id=1)
Standard Operating Procedures
SOP 1: Get All Sprints for a Project
# Step 1: Get project key (if only project name is known)
project = get_project(project_key="PROJ")
# Step 2: Find boards for this project
boards = get_all_boards(project_key_or_id="PROJ")
# Step 3: For each SCRUM board, get its sprints
for board in boards["values"]:
if board["type"] == "scrum":
sprints = get_board_sprints(board_id=board["id"])
Tool Sequence:
get_project(project_key)- Validate project existsget_all_boards(project_key_or_id)- Find scrum boardsget_board_sprints(board_id)- Get sprints for each board
SOP 2: Get Issues in Current Sprint
# Step 1: Get the board
boards = get_all_boards(project_key_or_id="PROJ", board_type="scrum")
board_id = boards["values"][0]["id"]
# Step 2: Get active sprint
sprints = get_board_sprints(board_id=board_id, state="active")
sprint_id = sprints["values"][0]["id"]
# Step 3: Get issues in that sprint
issues = get_sprint_issues(sprint_id=sprint_id)
Tool Sequence:
get_all_boards(project_key_or_id, board_type="scrum")- Find the boardget_board_sprints(board_id, state="active")- Get active sprintget_sprint_issues(sprint_id)- Get issues in sprint
SOP 3: Add Issue to Sprint
# Step 1: Ensure issue exists
issue = get_issue(issue_key="PROJ-123")
# Step 2: Find the target sprint
sprints = get_board_sprints(board_id=1, state="active")
sprint_id = sprints["values"][0]["id"]
# Step 3: Move issue to sprint
move_issues_to_sprint(sprint_id=sprint_id, issues=["PROJ-123"])
Tool Sequence:
get_issue(issue_key)- Validate issue existsget_board_sprints(board_id, state="active"|"future")- Find target sprintmove_issues_to_sprint(sprint_id, issues)- Add issues to sprint
SOP 4: Get Backlog Issues
# Step 1: Get the board
boards = get_all_boards(project_key_or_id="PROJ", board_type="scrum")
board_id = boards["values"][0]["id"]
# Step 2: Get backlog issues
backlog = get_backlog_issues(board_id=board_id)
Tool Sequence:
get_all_boards(project_key_or_id)- Find the boardget_backlog_issues(board_id)- Get backlog items
SOP 5: Create New Sprint
# Step 1: Get the board ID
boards = get_all_boards(project_key_or_id="PROJ", board_type="scrum")
board_id = boards["values"][0]["id"]
# Step 2: Create sprint on that board
sprint = create_sprint(
name="Sprint 42",
origin_board_id=board_id,
goal="Complete authentication module",
start_date="2025-01-15T00:00:00.000+00:00",
end_date="2025-01-29T00:00:00.000+00:00"
)
Tool Sequence:
get_all_boards(project_key_or_id, board_type="scrum")- Find the boardcreate_sprint(name, origin_board_id, goal, start_date, end_date)- Create sprint
Common Mistakes to Avoid
❌ Mistake 1: Trying to get sprints without board_id
# WRONG: There's no "get sprints by project" API
sprints = get_sprints_for_project("PROJ") # ❌ Doesn't exist!
# CORRECT: Get sprints via the board
boards = get_all_boards(project_key_or_id="PROJ", board_type="scrum")
sprints = get_board_sprints(board_id=boards["values"][0]["id"]) # ✅
❌ Mistake 2: Creating sprint without origin_board_id
# WRONG: origin_board_id is REQUIRED
sprint = create_sprint(name="Sprint 1") # ❌ Missing board!
# CORRECT: Always provide origin_board_id
sprint = create_sprint(name="Sprint 1", origin_board_id=1) # ✅
❌ Mistake 3: Expecting sprints on Kanban boards
# WRONG: Kanban boards don't have sprints
sprints = get_board_sprints(board_id=kanban_board_id) # ❌ Returns empty!
# CORRECT: Only query sprints for scrum boards
if board["type"] == "scrum":
sprints = get_board_sprints(board_id=board["id"]) # ✅
❌ Mistake 4: Not validating project/board exists first
# WRONG: Assuming project exists
sprints = get_board_sprints(board_id=999) # ❌ May fail with 404!
# CORRECT: Validate first
boards = get_all_boards(project_key_or_id="PROJ")
if boards.get("values"):
sprints = get_board_sprints(board_id=boards["values"][0]["id"]) # ✅
Sprint States
| State | Description | Transitions |
|---|---|---|
future | Sprint is planned but not started | → active |
active | Sprint is currently running (only ONE per board) | → closed |
closed | Sprint is completed | (final state) |
Agent Prompt Guidance
When the user asks about sprints, the agent should:
- Always clarify the project if not specified
- Find the board first before querying sprints
- Check board type - only scrum boards have sprints
- Handle pagination - boards/sprints/issues can have many results
Example Agent Response Pattern
User: "Show me the current sprint for project PROJ"
Agent thinking:
1. Need to find board(s) for project PROJ
2. Filter for scrum boards only
3. Get active sprint from the board
4. Get issues in that sprint
Agent actions:
1. get_all_boards(project_key_or_id="PROJ", board_type="scrum")
2. get_board_sprints(board_id=X, state="active")
3. get_sprint_issues(sprint_id=Y)
Real-World Query Examples
These are actual user queries and the correct SOP to handle them.
Query 1: "What is the sprint assigned to SDPL-687?"
User Intent: Get sprint information for a specific issue
SOP:
# Step 1: Get the issue with sprint field
issue = get_issue(
issue_key="SDPL-687",
fields="summary,status,sprint,customfield_10020" # sprint is often customfield_10020
)
# The sprint field will contain:
# - sprint name
# - sprint id
# - sprint state (active/closed/future)
Tool Sequence:
get_issue(issue_key="SDPL-687", fields="summary,status,sprint")
Expected Response:
Issue SDPL-687 is assigned to:
- Sprint: Cisco-FY26Q2-S8
- State: active
- Start: 2025-01-06
- End: 2025-01-20
Query 2: "Points completed per developer for sprints: Cisco-FY26Q2-S7, S8, S9"
User Intent: Aggregate story points by developer across multiple named sprints
SOP:
# Step 1: Find the project's scrum board
boards = get_all_boards(project_key_or_id="SDPL", board_type="scrum")
board_id = boards["values"][0]["id"]
# Step 2: Get ALL sprints for the board (to find by name)
all_sprints = get_board_sprints(board_id=board_id, max_results=100)
# Step 3: Filter sprints by name to get sprint IDs
target_sprint_names = ["Cisco-FY26Q2-S7", "Cisco-FY26Q2-S8", "Cisco-FY26Q2-S9"]
sprint_ids = {s["name"]: s["id"] for s in all_sprints["values"] if s["name"] in target_sprint_names}
# Step 4: For each sprint, get completed issues with points
for sprint_name, sprint_id in sprint_ids.items():
issues = get_sprint_issues(
sprint_id=sprint_id,
jql="status in (Done, Resolved, Closed)",
fields="summary,assignee,customfield_10021,status" # 10021 is often Story Points
)
# Aggregate points by assignee
for issue in issues["issues"]:
developer = issue["fields"]["assignee"]["displayName"]
points = issue["fields"].get("customfield_10021", 0) or 0
# Accumulate...
Tool Sequence:
get_all_boards(project_key_or_id="SDPL", board_type="scrum")- Get board IDget_board_sprints(board_id=X, max_results=100)- Get all sprints to find by nameget_sprint_issues(sprint_id=Y, jql="status in (Done,Resolved,Closed)", fields="assignee,customfield_10021")- For each sprint- Agent aggregates data and formats table
Expected Response:
| Developer Name | Cisco-FY26Q2-S7 | Cisco-FY26Q2-S8 | Cisco-FY26Q2-S9 |
|-------------------|-----------------|-----------------|-----------------|
| John Smith | 13 | 8 | 5 |
| Jane Doe | 8 | 13 | 10 |
| Bob Wilson | 5 | 5 | 8 |
Query 3: "Report of points resolved per developer for last 3 sprints in SDPL"
User Intent: Find recent sprints automatically, then aggregate
SOP:
# Step 1: Get the scrum board
boards = get_all_boards(project_key_or_id="SDPL", board_type="scrum")
board_id = boards["values"][0]["id"]
# Step 2: Get CLOSED sprints (most recent first)
# Note: API returns sprints in order, closed sprints are the completed ones
closed_sprints = get_board_sprints(board_id=board_id, state="closed", max_results=50)
# Step 3: Sort by end date descending, take last 3
# (sprints have completeDate field when closed)
recent_sprints = sorted(
closed_sprints["values"],
key=lambda s: s.get("completeDate", ""),
reverse=True
)[:3]
# Step 4: For each sprint, aggregate as in Query 2
Tool Sequence:
get_all_boards(project_key_or_id="SDPL", board_type="scrum")get_board_sprints(board_id=X, state="closed", max_results=50)- Get closed sprints- Agent sorts by completeDate to find "last 3"
get_sprint_issues(sprint_id=Y, ...)- For each of 3 sprints- Agent aggregates and formats
Common Issue: The agent may find OLD sprints if it doesn't sort by date!
Query 4: "Execute JQL and show Sprint/Points"
User Intent: Run specific JQL, extract sprint and points fields
SOP:
# Use search_issues with explicit fields
results = search_issues(
jql="project = SDPL AND sprint in (73390, 75033, 71590) ORDER BY created DESC",
fields="key,summary,sprint,customfield_10021", # Include sprint and points
max_results=100
)
# Format as table
for issue in results["issues"]:
key = issue["key"]
sprint = issue["fields"].get("sprint", [{}])[0].get("name", "No Sprint")
points = issue["fields"].get("customfield_10021", 0) or 0
print(f"| {key} | {sprint} | {points} |")
Tool Sequence:
search_issues(jql="...", fields="key,summary,sprint,customfield_10021")- Agent formats the table
Important: Sprint and Story Points are often custom fields! Common mappings:
sprintorcustomfield_10020- Sprint fieldcustomfield_10021orcustomfield_10026- Story Points
Field Discovery: Finding Sprint and Points Fields
The sprint and story points fields are custom fields that vary by Jira instance!
SOP to discover field IDs:
# Step 1: Get a known issue with sprint/points
issue = get_issue(issue_key="SDPL-687", fields="*all")
# Step 2: Look for fields containing "sprint" or "point"
# Common patterns:
# - customfield_10020: Sprint
# - customfield_10021: Story Points
# - customfield_10026: Story point estimate
# Step 3: Or use field discovery
fields = list_fields() # Returns all field metadata
sprint_field = next(f for f in fields if "sprint" in f["name"].lower())
points_field = next(f for f in fields if "story point" in f["name"].lower())
Sprint Naming Conventions
Many organizations use naming patterns. Help the agent understand:
| Pattern | Example | Meaning |
|---|---|---|
{Team}-FY{YY}Q{Q}-S{N} | Cisco-FY26Q2-S7 | Fiscal Year 26, Q2, Sprint 7 |
Sprint {N} | Sprint 42 | Simple numbering |
{Project} Sprint {N} | SDPL Sprint 15 | Project-prefixed |
{Date Range} | Jan 6-20 | Date-based |
When user says "last 3 sprints", the agent should:
- Get all sprints for the board
- Sort by
completeDate(for closed) orstartDate(for all) - Take the most recent N
Troubleshooting Common Failures
Issue: "No sprints found"
Cause: Wrong board, or board is Kanban (no sprints) Fix:
# Check board type first
boards = get_all_boards(project_key_or_id="SDPL")
for board in boards["values"]:
print(f"{board['name']}: {board['type']}") # Only 'scrum' has sprints
Issue: "Sprint field is empty"
Cause: Issue not assigned to any sprint (in backlog) Fix: Issue is valid, just not in a sprint
Issue: "Story points not showing"
Cause: Using wrong custom field ID Fix: Use field discovery to find the correct field ID for your instance
Issue: "Wrong sprints returned for 'last 3'"
Cause: Not sorting by date, or mixing board sprints Fix:
# Sort by completeDate for closed sprints
sprints = sorted(closed_sprints, key=lambda s: s["completeDate"], reverse=True)[:3]
Issue: "Points showing as null"
Cause: Issues don't have story points estimated
Fix: Filter for issues with points: jql="project = SDPL AND 'Story Points' > 0"
Agent Prompt Template for Sprint Queries
When user asks about sprints, the agent should:
1. IDENTIFY the project key from the query
- "SDPL-687" → project = "SDPL"
- "SDPL project" → project = "SDPL"
2. FIND the scrum board for that project
- get_all_boards(project_key_or_id="SDPL", board_type="scrum")
- If no scrum board → "This project uses Kanban, no sprints available"
3. GET sprints based on need:
- Specific sprint by NAME → get_board_sprints() then filter by name
- Last N sprints → get_board_sprints(state="closed"), sort by date
- Active sprint → get_board_sprints(state="active")
4. GET issues with required fields:
- get_sprint_issues(sprint_id, fields="assignee,customfield_10021")
- ALWAYS include sprint and points custom fields
5. AGGREGATE and FORMAT:
- Group by developer/assignee
- Sum story points
- Format as requested table
Related
- Spec: spec.md