MANDATORY before any GitHub project operations - caches project metadata to prevent rate limit exhaustion. Called by session-start. Other skills MUST use cached data.
/plugin marketplace add troykelly/claude-skills/plugin install issue-driven-development@troykelly-skillsThis skill is limited to using the following tools:
Cache GitHub project metadata ONCE at session start. All subsequent operations use cached data.
Core principle: Fetch once, extract many. Never repeat API calls for the same data.
This skill is called by session-start and provides cached data to all other skills.
GitHub GraphQL API has a 5,000 point/hour limit. Without caching:
gh project item-list = 1 call per invocationgh project field-list = 1 call per invocationgh project list = 1 call per invocationA typical session startup was consuming 3,500+ of 5,000 points through repeated calls.
Fetch project metadata ONCE and cache in environment variables. All skills use cached data.
Before any bulk operations, check available quota:
check_github_rate_limits() {
local graphql_remaining=$(gh api rate_limit --jq '.resources.graphql.remaining')
local graphql_reset=$(gh api rate_limit --jq '.resources.graphql.reset')
local rest_remaining=$(gh api rate_limit --jq '.resources.core.remaining')
echo "GraphQL: $graphql_remaining remaining"
echo "REST: $rest_remaining remaining"
if [ "$graphql_remaining" -lt 100 ]; then
local now=$(date +%s)
local wait_seconds=$((graphql_reset - now + 10))
echo "WARNING: GraphQL rate limit low. Resets in $wait_seconds seconds."
return 1
fi
return 0
}
Run this ONCE at session start. Store results in environment.
# === GitHub API Cache Initialization ===
# Cost: 2 GraphQL API calls (field-list + item-list)
echo "Caching GitHub project metadata..."
# Verify environment
if [ -z "$GITHUB_PROJECT_NUM" ] || [ -z "$GH_PROJECT_OWNER" ]; then
echo "ERROR: GITHUB_PROJECT_NUM and GH_PROJECT_OWNER must be set"
exit 1
fi
# CALL 1: Cache all project fields
export GH_CACHE_FIELDS=$(gh project field-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
# CALL 2: Cache all project items
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
# Extract field IDs from cached data (NO API CALLS)
export GH_STATUS_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .id')
export GH_TYPE_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Type") | .id // empty')
export GH_PRIORITY_FIELD_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Priority") | .id // empty')
# Extract status option IDs from cached data (NO API CALLS)
export GH_STATUS_BACKLOG_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Backlog") | .id')
export GH_STATUS_READY_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Ready") | .id')
export GH_STATUS_IN_PROGRESS_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "In Progress") | .id')
export GH_STATUS_IN_REVIEW_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "In Review") | .id')
export GH_STATUS_DONE_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Done") | .id')
export GH_STATUS_BLOCKED_ID=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Status") | .options[] | select(.name == "Blocked") | .id')
# Get project ID (needed for item-edit) - extract from cached fields response
# Note: If project ID not available in fields, this requires 1 additional call
export GH_PROJECT_ID=$(gh project list --owner "$GH_PROJECT_OWNER" --format json --limit 100 | \
jq -r ".projects[] | select(.number == $GITHUB_PROJECT_NUM) | .id")
echo "Cached: $(echo "$GH_CACHE_ITEMS" | jq '.items | length') project items"
echo "GraphQL calls used: 3"
These functions use ONLY cached data. NO API calls.
get_cached_item_id() {
local issue_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .id"
}
get_cached_status() {
local issue_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .status.name"
}
get_cached_issues_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.status.name == \"$status\") | .content.number"
}
get_cached_items_summary() {
echo "$GH_CACHE_ITEMS" | jq -r '.items[] | {number: .content.number, title: .content.title, status: .status.name}'
}
is_issue_in_project() {
local issue_num=$1
local item_id=$(get_cached_item_id "$issue_num")
[ -n "$item_id" ] && [ "$item_id" != "null" ]
}
Only refresh cache when you KNOW data has changed:
refresh_items_cache() {
# Cost: 1 API call
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
echo "Items cache refreshed"
}
Write operations cannot be cached. Use sparingly.
set_status_cached() {
local item_id=$1
local status_option_id=$2 # Use GH_STATUS_*_ID variables
gh project item-edit --project-id "$GH_PROJECT_ID" --id "$item_id" \
--field-id "$GH_STATUS_FIELD_ID" --single-select-option-id "$status_option_id"
}
# Example: Set to "In Progress"
# set_status_cached "$ITEM_ID" "$GH_STATUS_IN_PROGRESS_ID"
add_issue_to_project_cached() {
local issue_url=$1
gh project item-add "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --url "$issue_url"
# Refresh items cache after add
refresh_items_cache
}
# WRONG: 3 API calls for same data
STATUS_FIELD=$(gh project field-list ... | jq '.fields[] | select(.name == "Status")')
TYPE_FIELD=$(gh project field-list ... | jq '.fields[] | select(.name == "Type")')
PRIORITY_FIELD=$(gh project field-list ... | jq '.fields[] | select(.name == "Priority")')
# RIGHT: 0 API calls (uses cached data)
STATUS_FIELD=$(echo "$GH_CACHE_FIELDS" | jq '.fields[] | select(.name == "Status")')
TYPE_FIELD=$(echo "$GH_CACHE_FIELDS" | jq '.fields[] | select(.name == "Type")')
PRIORITY_FIELD=$(echo "$GH_CACHE_FIELDS" | jq '.fields[] | select(.name == "Priority")')
# WRONG: N API calls for N items
for issue in 1 2 3 4 5; do
gh project item-list ... | jq ".items[] | select(.content.number == $issue)"
done
# RIGHT: 0 API calls (uses cached data)
for issue in 1 2 3 4 5; do
echo "$GH_CACHE_ITEMS" | jq ".items[] | select(.content.number == $issue)"
done
# WRONG: API call every time you need to update status
STATUS_FIELD_ID=$(gh project field-list ... | jq -r '.fields[] | select(.name == "Status") | .id')
# RIGHT: Use cached environment variable
echo "$GH_STATUS_FIELD_ID"
When you need data not in the cache, prefer REST API (separate rate limit):
# REST API - uses separate 5,000/hour limit
gh api "repos/$OWNER/$REPO/issues/$ISSUE_NUM"
gh api "repos/$OWNER/$REPO/issues/$ISSUE_NUM/comments"
# MCP tools also use REST
mcp__github__get_issue(...)
mcp__github__list_issues(...)
After initialization, these are available:
| Variable | Contents |
|---|---|
GH_CACHE_FIELDS | Full project fields JSON |
GH_CACHE_ITEMS | Full project items JSON |
GH_PROJECT_ID | Project node ID |
GH_STATUS_FIELD_ID | Status field ID |
GH_TYPE_FIELD_ID | Type field ID |
GH_PRIORITY_FIELD_ID | Priority field ID |
GH_STATUS_BACKLOG_ID | Backlog option ID |
GH_STATUS_READY_ID | Ready option ID |
GH_STATUS_IN_PROGRESS_ID | In Progress option ID |
GH_STATUS_IN_REVIEW_ID | In Review option ID |
GH_STATUS_DONE_ID | Done option ID |
GH_STATUS_BLOCKED_ID | Blocked option ID |
| Operation | Before Caching | After Caching |
|---|---|---|
| Session init | 20-50 calls | 3 calls |
| Check issue status | 1 call | 0 calls |
| Get field IDs | 3 calls | 0 calls |
| Query by status | 1 call | 0 calls |
| Set status | 4 calls | 1 call |
| Sync verification | 10+ calls | 0 calls |
This skill is called by:
session-start - Initializes cache at session startThis skill's cached data is used by:
project-board-enforcement - All verification functionsissue-driven-development - Status updates and checksissue-prerequisite - Adding issues to projectepic-management - Epic operationsautonomous-orchestration - State queriesBefore any GitHub project operation:
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.