feat(governance): require board + status + priority + type on every issue

GitHub can't block issue creation on missing fields, so enforce continuously:

- .github/workflows/issue-governance.yml — on every issue event, ensure the
  issue is on the Dev Loop board (#7) and auto-fill any empty field (Status=Backlog,
  Priority=P2, Type=Task), then label needs-triage + comment so a human sets real
  values. Idempotent: only writes when a field is empty. Needs PROJECTS_TOKEN
  (project-scoped PAT) — default GITHUB_TOKEN can't write an org board.
- .github/ISSUE_TEMPLATE/ — Feature/Bug/Task forms set the native issue Type and
  add the issue to the board at creation; blank issues disabled so every issue
  starts typed + boarded.
This commit is contained in:
VincentShipsIt
2026-06-21 12:54:44 +02:00
parent 483f49af23
commit 58ce9c281c
5 changed files with 193 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
name: Bug report
description: Something isn't working
type: Bug
labels: ["bug"]
projects: ["shipshitdev/7"]
body:
- type: textarea
id: what
attributes:
label: What's wrong
description: What did you expect, and what happened instead?
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: Minimal steps. Include the skill/command and the agent (Claude Code, Codex, Cursor).
validations:
required: true
- type: textarea
id: context
attributes:
label: Environment
description: OS, agent + version, and anything else relevant.
validations:
required: false
- type: markdown
attributes:
value: >
Status and Priority are set on the **Dev Loop** board after creation
(auto-defaulted + `needs-triage` if you leave them).
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question / discussion
url: https://github.com/shipshitdev/skills/discussions
about: For open-ended questions, use Discussions. Issues are for tracked work.
+33
View File
@@ -0,0 +1,33 @@
name: Feature / enhancement
description: Propose a new skill, command, or capability
type: Feature
labels: ["enhancement"]
projects: ["shipshitdev/7"]
body:
- type: textarea
id: summary
attributes:
label: Summary
description: What should exist that doesn't yet?
validations:
required: true
- type: textarea
id: value
attributes:
label: Value
description: Why is this worth building? Who benefits and how?
validations:
required: true
- type: textarea
id: acceptance
attributes:
label: Acceptance criteria
description: How do we know it's done?
validations:
required: true
- type: markdown
attributes:
value: >
Status and Priority are set on the **Dev Loop** board after creation
(auto-defaulted + `needs-triage` if you leave them). Set the real values
and drop the label.
+22
View File
@@ -0,0 +1,22 @@
name: Task / chore
description: Maintenance, refactor, docs, or other tracked work
type: Task
projects: ["shipshitdev/7"]
body:
- type: textarea
id: what
attributes:
label: What needs doing
validations:
required: true
- type: textarea
id: why
attributes:
label: Why / context
validations:
required: false
- type: markdown
attributes:
value: >
Status and Priority are set on the **Dev Loop** board after creation
(auto-defaulted + `needs-triage` if you leave them).
+101
View File
@@ -0,0 +1,101 @@
name: Issue Governance
# Enforce that every issue always carries the four required fields:
# board membership · Status · Priority · Type
# GitHub cannot block issue creation on missing fields, so this auto-fills
# sane defaults for any empty field and flags the issue `needs-triage` for a
# human to set real values. Idempotent: it only writes when a field is empty,
# so once an issue is compliant it stops touching it.
#
# Board writes need a project-scoped PAT — the default GITHUB_TOKEN cannot
# read/write an org-owned Projects v2 board. Store it as the PROJECTS_TOKEN
# repo secret (same token the dev-loop workflows use).
on:
issues:
types: [opened, reopened, transferred, edited, labeled, unlabeled, demilestoned, assigned]
permissions:
issues: write
contents: read
concurrency:
group: issue-governance-${{ github.event.issue.number }}
cancel-in-progress: true
env:
PROJECT_OWNER: shipshitdev
PROJECT_NUMBER: "7" # Dev Loop board
DEFAULT_PRIORITY: "P2" # matched against the board's Priority options
DEFAULT_TYPE: "Task" # org issue type: Task | Bug | Feature
jobs:
enforce:
runs-on: ubuntu-latest
steps:
- name: Enforce board + status + priority + type
env:
GH_TOKEN: ${{ secrets.PROJECTS_TOKEN }}
ISSUE: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::PROJECTS_TOKEN secret is not set — cannot read/write the org board. See workflow header."
exit 1
fi
# --- resolve board ids ---
NODE=$(gh project view "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --format json | jq -r '.id')
FL=$(gh project field-list "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --format json)
SF=$(echo "$FL" | jq -r '.fields[]|select(.name=="Status").id')
SB=$(echo "$FL" | jq -r '.fields[]|select(.name=="Status").options[]|select(.name|test("Backlog")).id')
PF=$(echo "$FL" | jq -r '.fields[]|select(.name=="Priority").id')
PD=$(echo "$FL" | jq -r --arg p "$DEFAULT_PRIORITY" '.fields[]|select(.name=="Priority").options[]|select(.name|test($p)).id')
IURL=$(gh issue view "$ISSUE" -R "$REPO" --json url -q .url)
FLAGS=""
# --- 1. ensure the issue is on the board ---
ITEMS=$(gh project item-list "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --format json -L 800)
ITEM=$(echo "$ITEMS" | jq -r --arg u "$IURL" '.items[]|select(.content.url==$u).id' | head -1)
if [ -z "$ITEM" ]; then
ITEM=$(gh project item-add "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --url "$IURL" --format json | jq -r '.id')
FLAGS="${FLAGS}added to board; "
ITEMS=$(gh project item-list "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --format json -L 800)
fi
IV=$(echo "$ITEMS" | jq -r --arg u "$IURL" '.items[]|select(.content.url==$u)')
CUR_STATUS=$(echo "$IV" | jq -r '.status // ""')
CUR_PRI=$(echo "$IV" | jq -r '.priority // ""')
# --- 2. Status default ---
if [ -z "$CUR_STATUS" ]; then
gh project item-edit --id "$ITEM" --project-id "$NODE" --field-id "$SF" --single-select-option-id "$SB"
FLAGS="${FLAGS}Status=Backlog; "
fi
# --- 3. Priority default ---
if [ -z "$CUR_PRI" ]; then
gh project item-edit --id "$ITEM" --project-id "$NODE" --field-id "$PF" --single-select-option-id "$PD"
FLAGS="${FLAGS}Priority=${DEFAULT_PRIORITY} (default); "
fi
# --- 4. Type default (native issue type) ---
CUR_TYPE=$(gh issue view "$ISSUE" -R "$REPO" --json issueType -q '.issueType.name // ""')
if [ -z "$CUR_TYPE" ]; then
gh issue edit "$ISSUE" -R "$REPO" --type "$DEFAULT_TYPE"
FLAGS="${FLAGS}Type=${DEFAULT_TYPE} (default); "
fi
# --- 5. flag + comment only when something was auto-filled ---
if [ -n "$FLAGS" ]; then
gh issue edit "$ISSUE" -R "$REPO" --add-label needs-triage || true
{
echo "🤖 **Issue governance** auto-filled required fields: ${FLAGS%; }."
echo ""
echo "Set the real values — board **Status**/**Priority** and the issue **Type** — then remove the \`needs-triage\` label."
} | gh issue comment "$ISSUE" -R "$REPO" --body-file -
echo "Auto-filled: ${FLAGS%; }"
else
echo "Issue #$ISSUE already compliant — no changes."
fi