Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/pr-body-validator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: 📝 Validate PR Description

on:
pull_request:
types: [opened, edited, reopened]

jobs:
validate-pr-body:
runs-on: ubuntu-latest

steps:
- name: Validate PR Description
shell: bash
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
CLEAN_BODY=$(echo "$PR_BODY" | sed 's/\r$//')

if [ -z "${CLEAN_BODY// }" ]; then
echo "❌ PR description is completely empty." >&2
exit 1
fi

REQUIRED_FIELDS=("## Description of Task?" "## Any background context you want to provide?" "## What are the relevant user stories or tasks links from Azure DevOps?")
OPTIONAL_FIELD="## Screenshots (if appropriate)"

current_field=""
declare -A field_content
errors=()

# Parse PR body
while IFS= read -r line; do
trimmed=$(echo "$line" | xargs)
for field in "${REQUIRED_FIELDS[@]}" "$OPTIONAL_FIELD"; do
if [[ "${trimmed,,}" == "${field,,}" ]]; then
current_field="$field"
field_content["$current_field"]=""
continue 2
fi
done

if [ -n "$current_field" ]; then
if [ -z "${field_content[$current_field]}" ]; then
field_content["$current_field"]="$trimmed"
else
field_content["$current_field"]="${field_content[$current_field]}"$'\n'"$trimmed"
fi
fi
done <<< "$CLEAN_BODY"

for field in "${REQUIRED_FIELDS[@]}"; do
content="${field_content[$field]}"
if [ -z "$content" ]; then
errors+=("❌ Missing content for: $field")
elif echo "$content" | grep -iq "^N/A$"; then
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The grep pattern uses ^N/A$ which only matches exactly 'N/A' as the entire content, but the content variable may contain multiple lines or additional whitespace. This could miss variations like 'n/a', 'N/a', or 'N/A' with surrounding text.

Suggested change
elif echo "$content" | grep -iq "^N/A$"; then
elif echo "$content" | grep -iq '^\s*n/a\s*$'; then

Copilot uses AI. Check for mistakes.
errors+=("❌ Field '$field' has invalid content: N/A")
fi
done

if [ ${#errors[@]} -gt 0 ]; then
printf "%s\n" "${errors[@]}" >&2
exit 1
fi

echo "✅ PR description is valid."
35 changes: 35 additions & 0 deletions .github/workflows/validate-branch-name.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: 🔀 Validate Branch Name

on:
pull_request:
types: [opened, reopened]

jobs:
validate-branch-name:
runs-on: ubuntu-latest
steps:
- name: Validate Branch Name Format
shell: bash
env:
BRANCH_NAME: ${{ github.head_ref }}
run: |
echo "🔍 Validating branch name: $BRANCH_NAME"

# Allowed types: ft, bg, ch, rf
# Format: {type}-{summary}-{id}
BRANCH_REGEX='^(ft|bg|ch|rf)-[a-z0-9]+(-[a-z0-9]+){1,2}-[0-9]+$'
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern allows only 1-2 additional dash-separated segments, but the documentation on line 30 states '2–3 lowercase words' for the summary. This creates confusion about whether the summary can have 2 or 3 words.

Copilot uses AI. Check for mistakes.

if [[ "$BRANCH_NAME" =~ $BRANCH_REGEX ]]; then
echo "✅ Branch name format is valid."
else
echo "❌ Branch name format is invalid: \"$BRANCH_NAME\""
echo ""
echo "Expected format: {story-type}-{summary}-{task-id}"
echo "Where:"
echo " - story-type: ft, bg, ch, or rf"
echo " - summary: 2–3 lowercase words, dash-separated"
echo " - task-id: numeric Azure DevOps task ID"
echo ""
echo "Example: ft-add-auth-token-4521"
exit 1
fi
30 changes: 30 additions & 0 deletions .github/workflows/validate-commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: 🧪 Validate Commit Message

on:
push:

jobs:
validate-commit:
runs-on: ubuntu-latest
steps:
- name: Checkout current branch
uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed to see full commit history

- name: Get last non-merge commit message on current branch
id: get_commit
run: |
LAST_COMMIT=$(git log --no-merges -1 --pretty=%s)
echo "📝 Commit subject: $LAST_COMMIT"
echo "commit_msg=$LAST_COMMIT" >> $GITHUB_OUTPUT

- name: Validate commit message
run: |
echo "Validating: '${{ steps.get_commit.outputs.commit_msg }}'"
if [[ ! "${{ steps.get_commit.outputs.commit_msg }}" =~ ^(feat|fix|chore|docs|style|refactor|test|perf|ci|build|revert)(\(.+\))?:\ .+ ]]; then
echo "❌ Invalid commit message format."
echo "💡 Expected format: 'type(scope): description', e.g., 'feat(auth): add login API'"
Copy link

Copilot AI Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern has an inconsistency with the error message. The pattern includes 'chore' but the error message example shows 'type(scope): description' format. The commit message in the PR title uses 'ch(action):' which doesn't match either 'chore' or the documented types.

Suggested change
echo "💡 Expected format: 'type(scope): description', e.g., 'feat(auth): add login API'"
echo "💡 Expected format: 'type(scope): description', where type is one of feat, fix, chore, docs, style, refactor, test, perf, ci, build, revert. Example: 'feat(auth): add login API' or 'chore(deps): update dependencies'"

Copilot uses AI. Check for mistakes.
exit 1
fi
shell: bash
Loading