MANDATORY for all work - the project board is THE source of truth. This skill provides verification functions and gates that other skills MUST call. No work proceeds without project board compliance.
/plugin marketplace add troykelly/claude-skills/plugin install issue-driven-development@troykelly-skillsThis skill is limited to using the following tools:
The GitHub Project board is THE source of truth for all work state. Not labels. Not comments. Not memory. The project board.
Core principle: If it's not in the project board with correct fields, it doesn't exist.
This skill is called by other skills at gate points. It is not invoked directly.
CRITICAL: All read operations MUST use cached data from github-api-cache.
The following environment variables MUST be set before using this skill:
GH_CACHE_ITEMS - Cached project items JSONGH_CACHE_FIELDS - Cached project fields JSONGH_PROJECT_ID - Project node IDGH_STATUS_FIELD_ID - Status field IDGH_STATUS_*_ID - Status option IDsIf these are not set, invoke session-start first to initialize the cache.
Every issue, epic, and initiative MUST be in the project board BEFORE work begins.
This is not optional. This is not a suggestion. This is a hard gate.
# These MUST be set. Work cannot proceed without them.
echo $GITHUB_PROJECT # Full URL: https://github.com/users/USER/projects/N
echo $GITHUB_PROJECT_NUM # Just the number: N
echo $GH_PROJECT_OWNER # Owner: @me or org name
If any are missing, stop and configure them before proceeding.
Every project MUST have these fields configured:
| Field | Type | Required Values |
|---|---|---|
| Status | Single select | Backlog, Ready, In Progress, In Review, Done, Blocked |
| Type | Single select | Feature, Bug, Chore, Research, Spike, Epic, Initiative |
| Priority | Single select | Critical, High, Medium, Low |
| Field | Type | Purpose |
|---|---|---|
| Verification | Single select | Not Verified, Failing, Partial, Passing |
| Criteria Met | Number | Count of completed acceptance criteria |
| Criteria Total | Number | Total acceptance criteria |
| Last Verified | Date | When verification last ran |
| Epic | Text | Parent epic issue number |
| Initiative | Text | Parent initiative issue number |
All read operations use cached data (0 API calls). Only writes require API calls.
GATE FUNCTION - Called before any work begins. 0 API calls (uses cache).
verify_issue_in_project() {
local issue=$1
# Get project item ID FROM CACHE (0 API calls)
ITEM_ID=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue) | .id")
if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then
echo "BLOCKED: Issue #$issue is not in the project board."
echo ""
echo "Add it with:"
echo " gh project item-add $GITHUB_PROJECT_NUM --owner $GH_PROJECT_OWNER --url \$(gh issue view $issue --json url -q .url)"
return 1
fi
echo "$ITEM_ID"
return 0
}
GATE FUNCTION - Called before work proceeds past issue check. 0 API calls (uses cache).
verify_status_set() {
local issue=$1
local item_id=$2
# Get current status FROM CACHE (0 API calls)
STATUS=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.id == \"$item_id\") | .status.name")
if [ -z "$STATUS" ] || [ "$STATUS" = "null" ]; then
echo "BLOCKED: Issue #$issue has no Status set in project board."
echo ""
echo "Set status before proceeding."
return 1
fi
echo "$STATUS"
return 0
}
Called by issue-prerequisite after issue creation. 1 API call + cache refresh.
add_issue_to_project() {
local issue_url=$1
# Add to project (1 API call - unavoidable write)
gh project item-add "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --url "$issue_url"
if [ $? -ne 0 ]; then
echo "ERROR: Failed to add issue to project."
return 1
fi
# Refresh cache after adding (1 API call)
export GH_CACHE_ITEMS=$(gh project item-list "$GITHUB_PROJECT_NUM" --owner "$GH_PROJECT_OWNER" --format json)
# Get the item ID from refreshed cache
local issue_num=$(echo "$issue_url" | grep -oE '[0-9]+$')
ITEM_ID=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue_num) | .id")
echo "$ITEM_ID"
return 0
}
Called at every status transition. 1 API call (uses cached IDs).
set_project_status() {
local item_id=$1
local new_status=$2 # Backlog, Ready, In Progress, In Review, Done, Blocked
# Use cached IDs (0 API calls for lookups)
# GH_PROJECT_ID, GH_STATUS_FIELD_ID set by session-start
# Get option ID from cache
local option_id
case "$new_status" in
"Backlog") option_id="$GH_STATUS_BACKLOG_ID" ;;
"Ready") option_id="$GH_STATUS_READY_ID" ;;
"In Progress") option_id="$GH_STATUS_IN_PROGRESS_ID" ;;
"In Review") option_id="$GH_STATUS_IN_REVIEW_ID" ;;
"Done") option_id="$GH_STATUS_DONE_ID" ;;
"Blocked") option_id="$GH_STATUS_BLOCKED_ID" ;;
*)
# Fallback: look up from cached fields (0 API calls)
option_id=$(echo "$GH_CACHE_FIELDS" | jq -r ".fields[] | select(.name == \"Status\") | .options[] | select(.name == \"$new_status\") | .id")
;;
esac
if [ -z "$option_id" ] || [ "$option_id" = "null" ]; then
echo "ERROR: Status '$new_status' not found in project."
return 1
fi
# Single API call to update status
gh project item-edit --project-id "$GH_PROJECT_ID" --id "$item_id" \
--field-id "$GH_STATUS_FIELD_ID" --single-select-option-id "$option_id"
return $?
}
Called when creating issues. 1 API call (uses cached IDs).
set_project_type() {
local item_id=$1
local type=$2 # Feature, Bug, Chore, Research, Spike, Epic, Initiative
# Get type field ID and option from cache (0 API calls)
local type_field_id=$(echo "$GH_CACHE_FIELDS" | jq -r '.fields[] | select(.name == "Type") | .id')
local option_id=$(echo "$GH_CACHE_FIELDS" | jq -r ".fields[] | select(.name == \"Type\") | .options[] | select(.name == \"$type\") | .id")
if [ -z "$option_id" ] || [ "$option_id" = "null" ]; then
echo "ERROR: Type '$type' not found in project."
return 1
fi
# Single API call to update type
gh project item-edit --project-id "$GH_PROJECT_ID" --id "$item_id" \
--field-id "$type_field_id" --single-select-option-id "$option_id"
}
All queries use cached data. 0 API calls.
USE THIS instead of label queries. 0 API calls (uses cache).
get_issues_by_status() {
local status=$1 # Ready, In Progress, etc.
# Use cached data (0 API calls)
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.status.name == \"$status\") | .content.number"
}
# Examples:
# get_issues_by_status "Ready"
# get_issues_by_status "In Progress"
# get_issues_by_status "Blocked"
0 API calls (uses cache).
get_issues_by_type() {
local type=$1 # Epic, Feature, etc.
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.type.name == \"$type\") | .content.number"
}
0 API calls (uses cache).
get_epic_children() {
local epic_num=$1
echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.epic == \"#$epic_num\") | .content.number"
}
0 API calls (uses cache).
count_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq "[.items[] | select(.status.name == \"$status\")] | length"
}
These are the points in workflows where project board verification is MANDATORY:
| Workflow Point | Gate | Skill |
|---|---|---|
| Before any work | Issue in project | issue-driven-development Step 1 |
| After issue creation | Add to project, set fields | issue-prerequisite |
| Starting work | Status → In Progress | issue-driven-development Step 6 |
| Creating branch | Verify project membership | branch-discipline |
| PR created | Status → In Review | pr-creation |
| Work complete | Status → Done | issue-driven-development completion |
| Blocked | Status → Blocked | error-recovery |
| Epic created | Add epic to project, set Type=Epic | epic-management |
| Child issue created | Add to project, link to parent | issue-decomposition |
Backlog ──► Ready ──► In Progress ──► In Review ──► Done
│ │ │ │
│ │ │ │
└─────────┴────────────┴──────────────┴──► Blocked
│
▼
(any previous state)
validate_transition() {
local current=$1
local target=$2
case "$current→$target" in
"Backlog→Ready"|"Ready→In Progress"|"In Progress→In Review"|"In Review→Done")
return 0 ;;
*"→Blocked")
return 0 ;;
"Blocked→Backlog"|"Blocked→Ready"|"Blocked→In Progress")
return 0 ;;
*)
echo "Invalid transition: $current → $target"
return 1 ;;
esac
}
# WRONG - labels are NOT the source of truth
gh issue list --label "status:pending"
gh issue edit 123 --add-label "status:in-progress"
# RIGHT - project board IS the source of truth
get_issues_by_status "Ready"
set_project_status "$ITEM_ID" "In Progress"
Labels are still used for:
epic - Identifying epic issues (supplementary)epic-[name] - Grouping issues in an epic (supplementary)spawned-from:#N - Lineage tracking (supplementary)review-finding - Origin tracking (supplementary)But state (Ready, In Progress, Blocked, etc.) lives in the project board.
Run periodically to detect drift. 0 API calls (uses cache).
verify_project_sync() {
echo "## Project Board Sync Check"
echo ""
# Check for issues with branches but Status != In Progress (0 API calls)
echo "### Issues with branches but not 'In Progress':"
for branch in $(git branch -r | grep -E 'feature/[0-9]+' | sed 's/.*feature\///' | cut -d- -f1); do
# Use cached data instead of API call
status=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $branch) | .status.name")
if [ "$status" != "In Progress" ] && [ "$status" != "In Review" ]; then
echo "- #$branch: Status='$status' but has active branch"
fi
done
# Check for In Progress issues with no recent activity (0 API calls)
echo ""
echo "### 'In Progress' issues with no recent commits:"
for issue in $(get_issues_by_status "In Progress"); do
branch=$(git branch -r | grep -E "feature/$issue-" | head -1)
if [ -z "$branch" ]; then
echo "- #$issue: In Progress but no branch exists"
fi
done
}
All project board errors should be clear and actionable:
project_error() {
local code=$1
local context=$2
case "$code" in
"NOT_IN_PROJECT")
echo "BLOCKED: Issue $context is not in the project board."
echo "Fix: gh project item-add $GITHUB_PROJECT_NUM --owner $GH_PROJECT_OWNER --url \$(gh issue view $context --json url -q .url)"
;;
"NO_STATUS")
echo "BLOCKED: Issue $context has no Status field set."
echo "Fix: Update the issue's Status field in the project board."
;;
"INVALID_TRANSITION")
echo "BLOCKED: Cannot transition $context - invalid state change."
;;
"PROJECT_NOT_FOUND")
echo "BLOCKED: Project $GITHUB_PROJECT_NUM not found or not accessible."
echo "Fix: Verify GITHUB_PROJECT_NUM and GH_PROJECT_OWNER are correct."
;;
esac
return 1
}
This skill is called by:
issue-driven-development - All status transitionsissue-prerequisite - After issue creationepic-management - Epic and child issue setupautonomous-orchestration - State queries and updatessession-start - Sync verificationwork-intake - Project readiness checkThis skill requires cache from:
github-api-cache - Provides GH_CACHE_ITEMS, GH_CACHE_FIELDS, and field IDsBefore proceeding past any gate:
| Operation | Before Caching | After Caching |
|---|---|---|
| verify_issue_in_project | 1 call | 0 calls |
| verify_status_set | 1 call | 0 calls |
| add_issue_to_project | 2 calls | 2 calls |
| set_project_status | 4 calls | 1 call |
| set_project_type | 3 calls | 1 call |
| get_issues_by_status | 1 call | 0 calls |
| count_by_status | 1 call | 0 calls |
| verify_project_sync (10 branches) | 10 calls | 0 calls |
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.