
project-board-enforcement
by troykelly
Opinionated GitHub-native development workflow with 28 skills for autonomous, issue-driven software development with Claude Code
SKILL.md
name: project-board-enforcement description: 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. allowed-tools:
- Bash
- mcp__github__* model: opus
Project Board Enforcement
Overview
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.
API Optimization Requirement
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 IDs
If these are not set, invoke session-start first to initialize the cache.
The Rule
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.
Required Environment
# 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.
Project Field Requirements
Mandatory Fields
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 |
Recommended Fields
| 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 |
Verification Functions
All read operations use cached data (0 API calls). Only writes require API calls.
Verify Issue in Project
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
}
Verify Status Field Set
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
}
Add Issue to Project
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
}
Set Project Status
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 $?
}
Set Project Type
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"
}
State Queries via Project Board
All queries use cached data. 0 API calls.
Get Issues by Status
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"
Get Issues by Type
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"
}
Get Epic Children
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"
}
Count by Status
0 API calls (uses cache).
count_by_status() {
local status=$1
echo "$GH_CACHE_ITEMS" | jq "[.items[] | select(.status.name == \"$status\")] | length"
}
Gate Points
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 |
Transition Rules
Valid transitions:
Backlog → Ready → In Progress → In Review → Done
↓ ↓ ↓ ↓
└────────┴──────────┴────────────┴──→ Blocked
↓
(return to previous)
Transition Enforcement
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
}
Labels vs Project Board
WRONG: Using labels for state (status:in-progress)
RIGHT: Using project board Status field
Labels are only for supplementary info: epic, epic-[name], spawned-from:#N, review-finding
Sync Verification
Detect drift by comparing git branches to project board status:
- Issues with branches should be In Progress or In Review
- In Progress issues should have active branches
Use cached data (GH_CACHE_ITEMS) for 0 API calls. Example:
# Check if branch status matches project board
status=$(echo "$GH_CACHE_ITEMS" | jq -r ".items[] | select(.content.number == $issue) | .status.name")
Error Messages
All project board errors provide actionable fixes:
| Error Code | Message | Fix |
|---|---|---|
| NOT_IN_PROJECT | Issue not in project board | gh project item-add ... |
| NO_STATUS | Status field not set | Update Status field |
| INVALID_TRANSITION | Invalid state change | Use valid transition |
| PROJECT_NOT_FOUND | Project not accessible | Verify GITHUB_PROJECT_NUM |
Integration
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 check
This skill requires cache from:
github-api-cache- Provides GH_CACHE_ITEMS, GH_CACHE_FIELDS, and field IDs
Checklist for Callers
Before proceeding past any gate:
- GitHub API cache initialized (GH_CACHE_ITEMS, GH_CACHE_FIELDS set)
- Issue exists in project (verified from cache, not API call)
- Status field is set
- Type field is set
- Priority field is set (for new issues)
- Epic linkage set (if child of epic)
- Transition is valid (if changing status)
API Cost Summary
| 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 |
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


