git clone https://github.com/jmagly/aiwg
T=$(mktemp -d) && git clone --depth=1 https://github.com/jmagly/aiwg "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agentic/code/frameworks/sdlc-complete/skills/issue-list" ~/.claude/skills/jmagly-aiwg-issue-list-81d022 && rm -rf "$T"
agentic/code/frameworks/sdlc-complete/skills/issue-list/SKILL.mdIssue List
Purpose
List and filter tickets/issues from the configured ticketing provider (Gitea, GitHub, Jira, Linear) or local file-based tracking. Supports filtering by status, labels, assignee, and custom output formats.
Task
Given optional filter parameters:
- Load configuration from
or project.aiwg/config.yamlCLAUDE.md - Fetch tickets from provider or local files
- Apply filters (status, labels, assignee)
- Format output (table, JSON, or markdown)
- Display results with summary statistics
Parameters
(optional): Filter by status (open|in_progress|closed|blocked|review|all)--status STATE- Default:
(only show open tickets)open - Use
to show all tickets regardless of statusall
- Default:
(optional): Filter by label (can specify multiple:--label LABEL
)--label bug --label high-priority
(optional): Filter by assignee (use--assignee USER
for unassigned tickets)unassigned
(optional): Limit results to NUM tickets (default: 50)--limit NUM
(optional): Sort by field (created|updated|priority|id)--sort FIELD- Default:
(newest first)created
- Default:
(optional): Output format (table|json|markdown|compact)--format FORMAT- Default:
table
- Default:
(optional): Override configured provider--provider NAME
Inputs
Configuration sources (same as
/issue-create and /issue-update):
- Project-level configuration.aiwg/config.yaml
- User-level configurationCLAUDE.md- Default:
providerlocal
Outputs
Table Format (default):
┌──────────┬────────────────────────┬────────────┬──────────┬──────────┬────────────┐ │ ID │ Title │ Status │ Priority │ Assignee │ Labels │ ├──────────┼────────────────────────┼────────────┼──────────┼──────────┼────────────┤ │ ISSUE-1 │ Implement user auth │ in_progress│ high │ johndoe │ feature,ui │ │ ISSUE-2 │ Add dark mode │ open │ medium │ janedoe │ feature │ │ ISSUE-3 │ Fix navigation bug │ closed │ critical │ johndoe │ bug │ └──────────┴────────────────────────┴────────────┴──────────┴──────────┴────────────┘ Summary: 3 tickets (1 open, 1 in_progress, 1 closed)
Compact Format:
ISSUE-1 [in_progress] [high] Implement user auth @johndoe [feature,ui] ISSUE-2 [open] [medium] Add dark mode @janedoe [feature] ISSUE-3 [closed] [critical] Fix navigation bug @johndoe [bug] Summary: 3 tickets (1 open, 1 in_progress, 1 closed)
Markdown Format:
# Issues ## ISSUE-1: Implement user auth **Status**: in_progress **Priority**: high **Assignee**: @johndoe **Labels**: feature, ui **Created**: 2026-01-10 **Updated**: 2026-01-13 --- ## ISSUE-2: Add dark mode **Status**: open **Priority**: medium **Assignee**: @janedoe **Labels**: feature **Created**: 2026-01-11 **Updated**: 2026-01-11 --- ## Summary 3 tickets (1 open, 1 in_progress, 1 closed)
JSON Format:
{ "tickets": [ { "id": "ISSUE-1", "title": "Implement user auth", "status": "in_progress", "priority": "high", "assignee": "johndoe", "labels": ["feature", "ui"], "created": "2026-01-10", "updated": "2026-01-13", "url": "https://git.integrolabs.net/roctinam/ai-writing-guide/issues/1" }, { "id": "ISSUE-2", "title": "Add dark mode", "status": "open", "priority": "medium", "assignee": "janedoe", "labels": ["feature"], "created": "2026-01-11", "updated": "2026-01-11", "url": "https://git.integrolabs.net/roctinam/ai-writing-guide/issues/2" } ], "summary": { "total": 3, "open": 1, "in_progress": 1, "closed": 1 } }
Workflow
Step 1: Parse Parameters
Extract from command invocation:
# List all open tickets (default) /issue-list # List all tickets (including closed) /issue-list --status all # Filter by status /issue-list --status in_progress /issue-list --status closed # Filter by label /issue-list --label bug /issue-list --label feature --label high-priority # Filter by assignee /issue-list --assignee johndoe /issue-list --assignee unassigned # Combine filters /issue-list --status open --label bug --assignee johndoe # Limit results /issue-list --limit 10 # Sort by field /issue-list --sort updated /issue-list --sort priority # Change format /issue-list --format compact /issue-list --format json /issue-list --format markdown
Parameter extraction:
- All parameters optional
- Default:
--status open --limit 50 --format table --sort created
Step 2: Load Configuration
Same as
/issue-create command:
- Check
.aiwg/config.yaml - Fallback to
CLAUDE.md - Default to
providerlocal
Override with
--provider if specified.
Step 3: Fetch Issues (Provider-Specific)
Gitea
Use Gitea REST API:
# Build query parameters STATE="open" # or "closed" or "all" LABELS="${LABEL:-}" ASSIGNEE="${ASSIGNEE:-}" LIMIT="${LIMIT:-50}" SORT="created" # or "updated" ORDER="desc" # Fetch issues curl -s -H "Authorization: token $(cat ~/.config/gitea/token)" \ "${URL}/api/v1/repos/${OWNER}/${REPO}/issues?state=${STATE}&labels=${LABELS}&assignee=${ASSIGNEE}&limit=${LIMIT}&sort=${SORT}&order=${ORDER}"
Response mapping:
[ { "number": 1, "title": "Implement user auth", "state": "open", "labels": [{"name": "feature"}, {"name": "ui"}], "assignee": {"login": "johndoe"}, "created_at": "2026-01-10T10:00:00Z", "updated_at": "2026-01-13T15:30:00Z", "html_url": "https://git.integrolabs.net/roctinam/ai-writing-guide/issues/1" } ]
Map to internal format:
ID="ISSUE-${number}" TITLE="${title}" STATUS="${state}" # map to generic status PRIORITY="${extracted from body or labels}" ASSIGNEE="${assignee.login}" LABELS="${labels[].name joined by comma}" CREATED="${created_at}" UPDATED="${updated_at}" URL="${html_url}"
GitHub
Use
gh CLI:
# Build query STATE="${STATUS:-open}" # or "closed" or "all" LABELS="${LABEL:-}" ASSIGNEE="${ASSIGNEE:-}" LIMIT="${LIMIT:-50}" # Fetch issues gh issue list \ --repo "${OWNER}/${REPO}" \ --state "${STATE}" \ --label "${LABELS}" \ --assignee "${ASSIGNEE}" \ --limit "${LIMIT}" \ --json number,title,state,labels,assignees,createdAt,updatedAt,url
Response mapping:
[ { "number": 42, "title": "Implement user auth", "state": "OPEN", "labels": [{"name": "feature"}, {"name": "ui"}], "assignees": [{"login": "johndoe"}], "createdAt": "2026-01-10T10:00:00Z", "updatedAt": "2026-01-13T15:30:00Z", "url": "https://github.com/jmagly/aiwg/issues/42" } ]
Map to internal format:
ID="#${number}" TITLE="${title}" STATUS="${state}" # map OPEN → open, CLOSED → closed PRIORITY="${extracted from labels or body}" ASSIGNEE="${assignees[0].login}" LABELS="${labels[].name joined by comma}" CREATED="${createdAt}" UPDATED="${updatedAt}" URL="${url}"
Jira
Use Jira REST API v3:
# Build JQL query JQL="project = ${PROJECT_KEY}" # Add status filter if [ "${STATUS}" != "all" ]; then case "${STATUS}" in open) JQL="${JQL} AND status = 'To Do'" ;; in_progress) JQL="${JQL} AND status = 'In Progress'" ;; closed) JQL="${JQL} AND status = 'Done'" ;; blocked) JQL="${JQL} AND status = 'Blocked'" ;; review) JQL="${JQL} AND status = 'In Review'" ;; esac fi # Add label filter if [ -n "${LABEL}" ]; then JQL="${JQL} AND labels = '${LABEL}'" fi # Add assignee filter if [ -n "${ASSIGNEE}" ]; then if [ "${ASSIGNEE}" = "unassigned" ]; then JQL="${JQL} AND assignee is EMPTY" else JQL="${JQL} AND assignee = '${ASSIGNEE}'" fi fi # Fetch issues curl -s -u "${JIRA_EMAIL}:${JIRA_API_TOKEN}" \ "${JIRA_URL}/rest/api/3/search?jql=${JQL}&maxResults=${LIMIT}&fields=summary,status,priority,assignee,labels,created,updated"
Response mapping:
{ "issues": [ { "key": "PROJECT-123", "fields": { "summary": "Implement user auth", "status": {"name": "In Progress"}, "priority": {"name": "High"}, "assignee": {"displayName": "John Doe"}, "labels": ["feature", "ui"], "created": "2026-01-10T10:00:00.000+0000", "updated": "2026-01-13T15:30:00.000+0000" } } ] }
Map to internal format:
ID="${key}" TITLE="${fields.summary}" STATUS="${fields.status.name}" # map to generic status PRIORITY="${fields.priority.name}" ASSIGNEE="${fields.assignee.displayName}" LABELS="${fields.labels joined by comma}" CREATED="${fields.created}" UPDATED="${fields.updated}" URL="${JIRA_URL}/browse/${key}"
Linear
Use Linear GraphQL API:
# Build GraphQL query cat > /tmp/linear-query.json <<EOF { "query": "query { issues(filter: { team: { id: { eq: \"${TEAM_ID}\" } }, state: { name: { in: [\"${STATE_NAMES}\"] } } }, first: ${LIMIT}) { nodes { id identifier title state { name } priority priorityLabel assignee { name } labels { nodes { name } } createdAt updatedAt url } } }" } EOF # Fetch issues curl -s -X POST https://api.linear.app/graphql \ -H "Authorization: ${LINEAR_API_TOKEN}" \ -H "Content-Type: application/json" \ -d @/tmp/linear-query.json
Response mapping:
{ "data": { "issues": { "nodes": [ { "id": "abc123", "identifier": "ENG-42", "title": "Implement user auth", "state": {"name": "In Progress"}, "priority": 2, "priorityLabel": "High", "assignee": {"name": "John Doe"}, "labels": {"nodes": [{"name": "feature"}, {"name": "ui"}]}, "createdAt": "2026-01-10T10:00:00.000Z", "updatedAt": "2026-01-13T15:30:00.000Z", "url": "https://linear.app/team/issue/ENG-42" } ] } } }
Map to internal format:
ID="${identifier}" TITLE="${title}" STATUS="${state.name}" # map to generic status PRIORITY="${priorityLabel}" ASSIGNEE="${assignee.name}" LABELS="${labels.nodes[].name joined by comma}" CREATED="${createdAt}" UPDATED="${updatedAt}" URL="${url}"
Local
Read files from
.aiwg/issues/:
# Find all ticket files TICKET_FILES=(.aiwg/issues/ISSUE-*.md) # Read each file for TICKET_FILE in "${TICKET_FILES[@]}"; do # Extract metadata from frontmatter ID=$(grep "^id:" "${TICKET_FILE}" | awk '{print $2}') TITLE=$(grep "^title:" "${TICKET_FILE}" | cut -d':' -f2- | xargs) STATUS=$(grep "^status:" "${TICKET_FILE}" | awk '{print $2}') CREATED=$(grep "^created:" "${TICKET_FILE}" | awk '{print $2}') UPDATED=$(grep "^updated:" "${TICKET_FILE}" | awk '{print $2}') ASSIGNEE=$(grep "^assignee:" "${TICKET_FILE}" | awk '{print $2}') LABELS=$(grep "^labels:" "${TICKET_FILE}" | cut -d':' -f2- | xargs) PRIORITY=$(grep "^priority:" "${TICKET_FILE}" | awk '{print $2}') # Build ticket object # Add to results array done
Map to internal format:
ID="${id}" TITLE="${title}" STATUS="${status}" PRIORITY="${priority}" ASSIGNEE="${assignee}" LABELS="${labels}" CREATED="${created}" UPDATED="${updated}" URL=".aiwg/issues/${ID}.md" # file path
Step 4: Apply Filters
After fetching tickets, apply additional filters:
Status filter:
if [ "${STATUS_FILTER}" != "all" ]; then # Keep only tickets matching status FILTERED_TICKETS=$(filter_by_status "${TICKETS}" "${STATUS_FILTER}") fi
Label filter:
if [ -n "${LABEL_FILTER}" ]; then # Keep only tickets with matching label(s) FILTERED_TICKETS=$(filter_by_label "${TICKETS}" "${LABEL_FILTER}") fi
Assignee filter:
if [ -n "${ASSIGNEE_FILTER}" ]; then if [ "${ASSIGNEE_FILTER}" = "unassigned" ]; then # Keep only unassigned tickets FILTERED_TICKETS=$(filter_unassigned "${TICKETS}") else # Keep only tickets assigned to user FILTERED_TICKETS=$(filter_by_assignee "${TICKETS}" "${ASSIGNEE_FILTER}") fi fi
Sort:
case "${SORT}" in created) SORTED_TICKETS=$(sort_by_field "${TICKETS}" "created" "desc") ;; updated) SORTED_TICKETS=$(sort_by_field "${TICKETS}" "updated" "desc") ;; priority) SORTED_TICKETS=$(sort_by_priority "${TICKETS}") # critical → high → medium → low ;; id) SORTED_TICKETS=$(sort_by_field "${TICKETS}" "id" "asc") ;; esac
Limit:
if [ -n "${LIMIT}" ]; then FINAL_TICKETS=$(head -n "${LIMIT}" "${SORTED_TICKETS}") fi
Step 5: Format Output
Table Format
# Print header printf "┌──────────┬────────────────────────┬────────────┬──────────┬──────────┬────────────┐\n" printf "│ %-8s │ %-22s │ %-10s │ %-8s │ %-8s │ %-10s │\n" "ID" "Title" "Status" "Priority" "Assignee" "Labels" printf "├──────────┼────────────────────────┼────────────┼──────────┼──────────┼────────────┤\n" # Print rows for TICKET in "${FINAL_TICKETS[@]}"; do printf "│ %-8s │ %-22s │ %-10s │ %-8s │ %-8s │ %-10s │\n" \ "${ID}" \ "$(truncate_string "${TITLE}" 22)" \ "${STATUS}" \ "${PRIORITY}" \ "${ASSIGNEE}" \ "$(truncate_string "${LABELS}" 10)" done printf "└──────────┴────────────────────────┴────────────┴──────────┴──────────┴────────────┘\n"
Compact Format
for TICKET in "${FINAL_TICKETS[@]}"; do printf "%-10s [%-11s] [%-8s] %-30s @%-10s [%s]\n" \ "${ID}" \ "${STATUS}" \ "${PRIORITY}" \ "$(truncate_string "${TITLE}" 30)" \ "${ASSIGNEE}" \ "${LABELS}" done
Markdown Format
echo "# Issues" echo "" for TICKET in "${FINAL_TICKETS[@]}"; do echo "## ${ID}: ${TITLE}" echo "" echo "**Status**: ${STATUS}" echo "**Priority**: ${PRIORITY}" echo "**Assignee**: @${ASSIGNEE}" echo "**Labels**: ${LABELS}" echo "**Created**: ${CREATED}" echo "**Updated**: ${UPDATED}" echo "" echo "---" echo "" done
JSON Format
cat <<EOF { "tickets": [ $(for TICKET in "${FINAL_TICKETS[@]}"; do cat <<TICKET_JSON { "id": "${ID}", "title": "${TITLE}", "status": "${STATUS}", "priority": "${PRIORITY}", "assignee": "${ASSIGNEE}", "labels": [$(echo "${LABELS}" | sed 's/,/", "/g' | sed 's/^/"/' | sed 's/$/"/')], "created": "${CREATED}", "updated": "${UPDATED}", "url": "${URL}" }$([ "${TICKET}" != "${FINAL_TICKETS[-1]}" ] && echo ",") TICKET_JSON done) ], "summary": { "total": ${TOTAL_COUNT}, "open": ${OPEN_COUNT}, "in_progress": ${IN_PROGRESS_COUNT}, "closed": ${CLOSED_COUNT}, "blocked": ${BLOCKED_COUNT}, "review": ${REVIEW_COUNT} } } EOF
Step 6: Display Summary Statistics
After displaying tickets, show summary:
echo "" echo "Summary: ${TOTAL_COUNT} tickets (${OPEN_COUNT} open, ${IN_PROGRESS_COUNT} in_progress, ${CLOSED_COUNT} closed)" if [ -n "${LABEL_FILTER}" ]; then echo "Filtered by label: ${LABEL_FILTER}" fi if [ -n "${ASSIGNEE_FILTER}" ]; then echo "Filtered by assignee: ${ASSIGNEE_FILTER}" fi if [ "${STATUS_FILTER}" != "all" ]; then echo "Filtered by status: ${STATUS_FILTER}" fi
Examples
Example 1: List All Open Issues (Default)
Command:
/issue-list
Config (
.aiwg/config.yaml):
ticketing: provider: gitea url: https://git.integrolabs.net owner: roctinam repo: ai-writing-guide
Output:
┌──────────┬────────────────────────┬────────────┬──────────┬──────────┬────────────┐ │ ID │ Title │ Status │ Priority │ Assignee │ Labels │ ├──────────┼────────────────────────┼────────────┼──────────┼──────────┼────────────┤ │ ISSUE-1 │ Implement user auth │ in_progress│ high │ johndoe │ feature,ui │ │ ISSUE-2 │ Add dark mode │ open │ medium │ janedoe │ feature │ │ ISSUE-4 │ Security audit │ open │ critical │ security │ security │ └──────────┴────────────────────────┴────────────┴──────────┴──────────┴────────────┘ Summary: 3 tickets (2 open, 1 in_progress, 0 closed)
Example 2: List Closed Issues
Command:
/issue-list --status closed
Output:
┌──────────┬────────────────────────┬────────────┬──────────┬──────────┬────────────┐ │ ID │ Title │ Status │ Priority │ Assignee │ Labels │ ├──────────┼────────────────────────┼────────────┼──────────┼──────────┼────────────┤ │ ISSUE-3 │ Fix navigation bug │ closed │ critical │ johndoe │ bug │ │ ISSUE-5 │ Update documentation │ closed │ low │ janedoe │ docs │ └──────────┴────────────────────────┴────────────┴──────────┴──────────┴────────────┘ Summary: 2 tickets (0 open, 0 in_progress, 2 closed) Filtered by status: closed
Example 3: List Bugs Assigned to User
Command:
/issue-list --label bug --assignee johndoe
Output:
┌──────────┬────────────────────────┬────────────┬──────────┬──────────┬────────────┐ │ ID │ Title │ Status │ Priority │ Assignee │ Labels │ ├──────────┼────────────────────────┼────────────┼──────────┼──────────┼────────────┤ │ ISSUE-3 │ Fix navigation bug │ closed │ critical │ johndoe │ bug │ │ ISSUE-6 │ Fix auth timeout │ open │ high │ johndoe │ bug │ └──────────┴────────────────────────┴────────────┴──────────┴──────────┴────────────┘ Summary: 2 tickets (1 open, 0 in_progress, 1 closed) Filtered by label: bug Filtered by assignee: johndoe
Example 4: List Unassigned Issues (Compact Format)
Command:
/issue-list --assignee unassigned --format compact
Output:
ISSUE-2 [open] [medium] Add dark mode @unassigned [feature] ISSUE-7 [open] [low] Refactor API module @unassigned [refactor] ISSUE-8 [blocked] [high] Deploy to staging @unassigned [deployment,blocked] Summary: 3 tickets (2 open, 0 in_progress, 0 closed, 1 blocked) Filtered by assignee: unassigned
Example 5: List All Issues (JSON Format)
Command:
/issue-list --status all --format json --limit 2
Output:
{ "tickets": [ { "id": "ISSUE-1", "title": "Implement user auth", "status": "in_progress", "priority": "high", "assignee": "johndoe", "labels": ["feature", "ui"], "created": "2026-01-10", "updated": "2026-01-13", "url": "https://git.integrolabs.net/roctinam/ai-writing-guide/issues/1" }, { "id": "ISSUE-2", "title": "Add dark mode", "status": "open", "priority": "medium", "assignee": "janedoe", "labels": ["feature"], "created": "2026-01-11", "updated": "2026-01-11", "url": "https://git.integrolabs.net/roctinam/ai-writing-guide/issues/2" } ], "summary": { "total": 8, "open": 3, "in_progress": 1, "closed": 2, "blocked": 1, "review": 1 } }
Example 6: List High-Priority Issues (Markdown Format)
Command:
/issue-list --status all --format markdown | grep -A10 "Priority\*\*: high"
Output:
## ISSUE-1: Implement user auth **Status**: in_progress **Priority**: high **Assignee**: @johndoe **Labels**: feature, ui **Created**: 2026-01-10 **Updated**: 2026-01-13 --- ## ISSUE-6: Fix auth timeout **Status**: open **Priority**: high **Assignee**: @johndoe **Labels**: bug **Created**: 2026-01-12 **Updated**: 2026-01-12 ---
Error Handling
No Issues Found
No tickets found. Filters applied: - Status: open - Label: bug - Assignee: johndoe Try: - Remove filters: /issue-list - Change status: /issue-list --status all - Create ticket: /issue-create "title"
Provider Error
❌ Failed to fetch tickets from Gitea: Error: 401 Unauthorized - Token may be invalid or expired - Verify token at https://git.integrolabs.net/user/settings/applications Falling back to local provider...
Invalid Filter
❌ Invalid status filter: 'inprogress' Valid status values: - open - in_progress - closed - blocked - review - all Example: /issue-list --status in_progress
Best Practices
- Use filters wisely - Narrow results to relevant tickets
- Default to open tickets - Focus on actionable work
- Use compact format for quick scans - Easy to parse visually
- Use JSON format for automation - Parse with jq or scripts
- Use markdown format for reports - Copy to documentation
- Sort by updated - See recently active tickets first
- Limit results - Avoid overwhelming output
- Combine filters - Find exactly what you need (e.g.,
)--label bug --assignee unassigned
Integration with SDLC Workflows
Daily Standup:
# What am I working on? /issue-list --assignee me --status in_progress # What's blocked? /issue-list --status blocked
Sprint Planning:
# What's in the backlog? /issue-list --status open --sort priority # What's unassigned? /issue-list --assignee unassigned --label feature
Bug Triage:
# Critical bugs /issue-list --label bug --status open --sort priority # Unassigned bugs /issue-list --label bug --assignee unassigned
Security Review:
# Security tickets /issue-list --label security --status all # Security vulnerabilities /issue-list --label vulnerability --status open
Retrospective:
# Closed this sprint /issue-list --status closed --sort updated --limit 20 # Blocked items /issue-list --status blocked
References
- @$AIWG_ROOT/agentic/code/frameworks/sdlc-complete/config/issueing-config.md - Configuration schema
- @$AIWG_ROOT/agentic/code/frameworks/sdlc-complete/commands/issue-create.md - Create ticket command
- @$AIWG_ROOT/agentic/code/frameworks/sdlc-complete/commands/issue-update.md - Update ticket command
- @CLAUDE.md - User ticketing configuration