Repository: docker-seminar/docker-tutorial
Pull request #25: infra(CI): Simplify Title Validation for PRs and Commits Using parse-title.sh
Author: login: kimseungbin
Created: 2025-03-23T14:38:38Z | Merged: 2025-03-23T14:56:06Z

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a unified title validation process for pull requests and commits that provides clear, consistent feedback on formatting.

- **Chores**
  - Streamlined the validation workflows by delegating title parsing to an external utility, resulting in simplified processes and improved maintainability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

- Close #24

Linked issue #24: infra(CI): Fix PR Title Check
# Details

- Fix scope checking logic
- Separate duplicate logic into separate shell script


Diff:
diff --git a/.github/workflows/pr-title-check.yaml b/.github/workflows/pr-title-check.yaml
index b58ce10..44f403e 100644
--- a/.github/workflows/pr-title-check.yaml
+++ b/.github/workflows/pr-title-check.yaml
@@ -26,73 +26,14 @@ jobs:
         run: |
           echo "Validating PR title: '$PR_TITLE'"
           
-          # Extract keyword, scope, and description from the PR title
-          PR_PATTERN='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
-          if [[ ! "$PR_TITLE" =~ $PR_PATTERN ]]; then
-            COMMENT_BODY="> [!Warning]\n> Invalid PR title format. Expected: <type>(<scope>): <description>"
-            gh pr comment "$PR_NUMBER" --body "$COMMENT_BODY"
-            echo "::error::Invalid PR title format. Expected: <type>(<scope>): <description>"
+          # Check if the child script reported an error
+          if ! source infra/scripts/conventional-titles/parse-title.sh "$PR_TITLE"; then
+            gh pr comment ${{ github.event.pull_request.number }} --body "> [!WARNING]
+            > PR title validation failed!
+            $MESSAGES"
             exit 1
+          else
+            gh pr comment ${{ github.event.pull_request.number }} --body "> [!NOTE]
+            > PR title validation succeeded.
+            $MESSAGES"
           fi
-
-          PR_KEYWORD="${BASH_REMATCH[1]}"
-          PR_SCOPE="${BASH_REMATCH[2]}"
-          PR_DESCRIPTION="${BASH_REMATCH[3]}"
-          
-          echo "Keyword: $PR_KEYWORD"
-          echo "Scope: $PR_SCOPE"
-          echo "Description: $PR_DESCRIPTION"
-          
-          # Parse JSON from validation.yaml
-          # Configuration file location
-          validation_yaml="infra/config/validation.yaml"
-          
-          # Confirm the keyword exists.
-          keyword_exists="$(
-            yq e ".allowed_keywords[] | select(.name == \"$PR_KEYWORD\") | .name" \
-            "$validation_yaml"
-          )"
-          
-          if [[ -z "$keyword_exists" ]]; then
-          
-            # Print out the list of all allowed keywords as comma-separated on one line
-            allowed_keywords="$(
-              yq e '.allowed_keywords[].name' "$validation_yaml" \
-              | parse -sd ',' -
-            )"
-          
-            echo "ℹ️ Allowed keywords are: $allowed_keywords"
-
-            exit 1
-          fi
-          
-          # Check if the keyword required a scope
-          REQUIRES_SCOPE=$(echo "$ALLOWED_KEYWORDS_JSON" | jq -r --arg key "$PR_KEYWORD" '.[] | select(.name == $key) | .requires_scope')
-
-          if [[ "$REQUIRES_SCOPE" == "true" && -z "$PR_SCOPE" ]]; then
-            echo "::error::PR title must include a scope for '$PR_KEYWORD'."
-            exit 1
-          fi
-          
-          # Validate scope if provided
-          if [[ -n "$PR_SCOPE" ]]; then
-            VALID_SCOPES=$(echo "$ALLOWED_KEYWORDS_JSON" | jq -r --arg key "$PR_KEYWORD" '.[] | select(.name == $key) | .scopes[]')
-            if ! echo "$VALID_SCOPES" | grep -qw "$PR_SCOPE"; then
-              echo "::error::Invalid scope '$PR_SCOPE' for '$PR_KEYWORD'. Allowed scopes: $VALID_SCOPES"
-              echo "INVALID_SCOPE=1" >> $GITHUB_ENV
-              echo "VALID_SCOPES='$VALID_SCOPES'" >> $GITHUB_ENV
-              exit 1  # Still fail the workflow
-            fi
-          fi
-          
-          echo "::notice::PR title is valid!"
-      - name: Comment on PR if scope is invalid
-        if: env.INVALID_SCOPE == '1'
-        run: |
-          gh pr comment ${{ github.event.pull_request.number }} --body "> [!WARNING]
-          > **PR Scope Warning**
-          The scope \`$PR_SCOPE\` is not an allowed scope for \`$PR_KEYWORD\`.
-          **Allowed scopes:** $VALID_SCOPES
-          
-          > [!TIP]
-          > If this scope should be valid, update \`infra/config/validation.yaml\`."
diff --git a/infra/git/hooks/commit-msg b/infra/git/hooks/commit-msg
index f4e62d7..d81c567 100755
--- a/infra/git/hooks/commit-msg
+++ b/infra/git/hooks/commit-msg
@@ -70,103 +70,14 @@ echo "yq is installed."
 
 # Extract the first line (title)
 commit_title=$(echo "$commit_msg" | head -n 1)
-
-# Extract keyword, scope, and description from the commit title
-COMMIT_PATTERN='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
-
 echo "Validating commit title: $commit_title"
 
-# Check if commit title matches the full pattern
-if [[ ! "$commit_title" =~ $COMMIT_PATTERN ]]; then
-  echo "❌ Invalid commit message format! Expected: <type>(<scope>): <description>"
-
-  # Debugging: Check where it failed
-  if ! [[ "$commit_title" =~ ^[a-z]+ ]]; then
-    echo "🔍 Error: The keyword is missing or not lowercase."
-  fi
-
-  if ! [[ "$commit_title" =~ \(([A-Za-z0-9/-]+)\) ]]; then
-    echo "🔍 Error: Scope is missing or has invalid characters."
-  fi
-
-  if ! [[ "$commit_title" =~ :\  ]]; then
-    echo "🔍 Error: The colon separator is missing or incorrect."
-  fi
-
-  if ! [[ "$commit_title" =~ :\ (.+)$ ]]; then
-    echo "🔍 Error: Description is missing after ':'."
-  fi
-
-  exit 1
-fi
-
-# Debugging: Show extracted values
-COMMIT_KEYWORD="${BASH_REMATCH[1]}"
-COMMIT_SCOPE_RAW="${BASH_REMATCH[2]}"  # e.g. "(Git)"
-COMMIT_DESCRIPTION="${BASH_REMATCH[3]}"
-
-# Use parameter expansion to remove '(' and ')'
-COMMIT_SCOPE="${COMMIT_SCOPE_RAW#(}"   # e.g. "Git)"
-COMMIT_SCOPE="${COMMIT_SCOPE%)}"       # e.g. "Git"
-
-echo "Keyword: $COMMIT_KEYWORD"
-echo "Scope: $COMMIT_SCOPE"
-echo "Description: $COMMIT_DESCRIPTION"
-
-# Confirm the keyword exists.
-keyword_exists="$(
-  yq e ".allowed_keywords[] | select(.name == \"$COMMIT_KEYWORD\") | .name" \
-  "$validation_yaml"
-)"
-
-if [[ -z "$keyword_exists" ]]; then
-  echo "❌ Invalid keyword: $COMMIT_KEYWORD"
-
-  # Print out the list of all allowed keywords as comma-separated on one line
-  allowed_keywords="$(
-    yq e '.allowed_keywords[].name' "$validation_yaml" \
-    | paste -sd ',' -
-  )"
-
-  echo "✅ Allowed keywords are: $allowed_keywords"
-
+# Check if the child script reported an error.
+if ! source infra/scripts/conventional-titles/parse-title.sh "$commit_title"; then
+  echo "Commit title validation failed:" >&2
+  echo "$MESSAGES" >&2
   exit 1
-fi
-
-# Check requires_scope
-keyword_requires_scope="$(
-  yq e ".allowed_keywords[] | select(.name == \"$COMMIT_KEYWORD\") | .requires_scope" \
-    "$validation_yaml"
-)"
-
-if [[ "$keyword_requires_scope" == "true" ]]; then
-  if [[ -z "$COMMIT_SCOPE" ]]; then
-    echo "❌ The keyword '$COMMIT_KEYWORD' requires a scope, but none was provided."
-    exit 1
-  fi
-
-  # Check allowed scopes
-  allowed_scopes="$(
-    yq e ".allowed_keywords[] | select(.name == \"$COMMIT_KEYWORD\") | .scopes[]" "$validation_yaml"
-  )"
 
-  allowed_scopes_csv="$(
-    echo "$allowed_scopes" \
-    | paste -sd ',' -
-  )"
-
-  if ! echo "$allowed_scopes" | grep -qx "$COMMIT_SCOPE"; then
-    echo "❌ Scope '$COMMIT_SCOPE' is not in the allowed scopes for '$COMMIT_KEYWORD'."
-    echo "✅ Allowed scopes are:"
-    echo "$allowed_scopes_csv"
-    exit 1
-  fi
-
-else # requires_scope == "false"
-  if [[ -n "$COMMIT_SCOPE" ]]; then
-    echo "❌ The keyword '$COMMIT_KEYWORD' does not allow a scope, but one was provided ($COMMIT_SCOPE)."
-    exit 1
-  fi
-fi
-
-echo "✅ Valid commit message!"
+else
+  echo "$MESSAGES"
+fi
\ No newline at end of file
diff --git a/infra/scripts/conventional-titles/parse-title.sh b/infra/scripts/conventional-titles/parse-title.sh
new file mode 100755
index 0000000..e97288d
--- /dev/null
+++ b/infra/scripts/conventional-titles/parse-title.sh
@@ -0,0 +1,148 @@
+#!/bin/bash
+
+# This script validates and parses a commit/PR title provided as an argument.
+# The title is expected to be one of the following format:
+#   - <type>(<scope>): <description>
+#   - <type>: <description>
+#
+# It extracts and sets the following environment variables:
+#  - KEYWORD:      The commit/PR type (e.e., `feat`, `fix`).
+#   - SCOPE:        The scope without surrounding parentheses (if provided).
+#   - DESCRIPTION:  The description after the colon.
+#   - MESSAGES:     A log of messages generated during validation.
+#
+# Usage:
+#   ./parse-title.sh "your title string"
+#
+
+echo "Parsing title..."
+
+# Retrieve the title from the first argument.
+TITLE="$1"
+
+# Ensure a title was provided.
+if [ -z "$TITLE" ]; then
+  echo "❌ Title not provided, Please pass a title as the first argument." >&2
+  exit 1
+fi
+
+# Global variable to collect messages.
+MESSAGES=""
+
+# Log messages by appending it to MESSAGES variable.
+log_message() {
+  MESSAGES+="$1"$'\n'
+}
+
+validate_syntax() {
+  local title="$1"
+  # Ideally, a non-capturing group would be used for the optional scope
+  # since the scope itself needs to be captured not the surrounding parentheses.
+  # However, some OSs do not support non-capturing groups in bash.
+  # As a workaround, this pattern captures the scope including its parentheses
+  # which are removed later.
+  # For environments that support non-capturing groups, you could use:
+  # pattern='^([a-z]+)(?:(\([A-Za-z0-9]+\)))?: (.+)$'
+  local pattern='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
+
+  if [[ ! "$title" =~ $pattern ]]; then
+    log_message "❌Invalid title format."
+    log_message "🔍Expected: <type>(<scope>): <description> or <type>: <description>"
+
+    # Debugging: Check where it failed.
+    if ! [[ "$title" =~ ^[a-z]+ ]]; then
+      log_message "🔍Keyword is missing or now lowercase"
+    fi
+
+    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)\) ]]; then
+      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
+    fi
+
+    if ! [[ "$title" =~ :\  ]]; then
+      log_message "🔍The colon separator is missing or incorrect."
+    fi
+
+    if ! [[ "$title" =~ :\ (.+)$ ]]; then
+      log_message "🔍 Description is missing after ':'."
+    fi
+
+    return 1
+  else
+
+    KEYWORD="${BASH_REMATCH[1]}"
+
+    local raw_scope="${BASH_REMATCH[2]}"
+
+    # If raw scope is non-empty, remove surrounding parentheses.
+    if [[ -n "$raw_scope" ]]; then
+      raw_scope="${raw_scope#\(}" # Remove leading '('
+      raw_scope="${raw_scope%\)}" # Remove trailing ')'
+    fi
+
+    SCOPE="$raw_scope"
+    DESCRIPTION="${BASH_REMATCH[3]}"
+
+      if [[ ! "$DESCRIPTION" =~ ^[A-Z] ]]; then
+        log_message "🔍Description must start with a capital letter."
+        return 1
+      fi
+
+    log_message "✅Valid title format."
+    log_message "ℹ️**KEYWORD**: $KEYWORD"
+    log_message "ℹ️**SCOPE**: $SCOPE"
+    log_message "ℹ️**DESCRIPTION**: $DESCRIPTION"
+  fi
+
+  return 0
+}
+
+validate_semantics() {
+  local validation_yaml="infra/config/validation.yaml"
+
+  local keyword_exists
+  keyword_exists="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .name" "$validation_yaml")"
+
+  if [[ -z "$keyword_exists" ]]; then
+    log_message "❌ Invalid keyword: $KEYWORD"
+
+    local allowed_keywords_csv
+    allowed_keywords_csv="$(yq e ".allowed_keywords[].name" "$validation_yaml" | paste -sd ',' -)"
+    log_message "ℹ️Allowed keywords are: $allowed_keywords_csv"
+
+    return 1
+  fi
+
+  local keyword_requires_scope
+  keyword_requires_scope="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .requires_scope" "$validation_yaml")"
+
+  if [[ "$keyword_requires_scope" == "true" ]]; then
+    if [[ -z "$SCOPE" ]]; then
+      log_message "❌ The keyword '$KEYWORD' requires a scope, but none was provided."
+      return 1
+    fi
+
+    # Check allowed scopes
+    local allowed_scopes
+    allowed_scopes="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml")"
+
+    if ! echo "$allowed_scopes" | grep -qx "$SCOPE"; then
+      local allowed_scopes_csv
+      allowed_scopes_csv="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml")"
+
+      log_message "❌Scope '$SCOPE' is not in the allowed scopes for keyword '$KEYWORD'."
+      log_message "ℹ️Allowed scopes are:$allowed_scopes_csv"
+      return 1
+    fi
+
+  # requires_scope == "false"
+  else
+    if [[ -n "$SCOPE" ]]; then
+      log_message "ℹ️The keyword '$KEYWORD' does not require a scope, but one was provided scope '$SCOPE'."
+    fi
+  fi
+}
+
+validate_syntax "$TITLE" || return 1
+validate_semantics || return 1
+
+return 0
\ No newline at end of file


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>.github/workflows/pr-title-check.yaml (1)</summary><blockquote>

`29-39`: **Good refactoring of PR title validation logic!**

The move to an external script improves maintainability by centralizing validation logic.


However, consider enhancing error handling to avoid comment pollution:

```diff
- gh pr comment ${{ github.event.pull_request.number }} --body "> [!WARNING]
- > PR title validation failed!
- $MESSAGES"
+ gh pr comment ${{ github.event.pull_request.number }} --body "> [!WARNING]
+ > PR title validation failed!
+ $MESSAGES" --edit-last || gh pr comment ${{ github.event.pull_request.number }} --body "> [!WARNING]
+ > PR title validation failed!
+ $MESSAGES"
```

This would attempt to edit the last comment if it exists, or create a new one if not.

</blockquote></details>
<details>
<summary>infra/git/hooks/commit-msg (1)</summary><blockquote>

`75-83`: **Excellent refactoring of commit message validation!**

The changes effectively centralize the validation logic into a separate script, improving maintainability.


Consider adding a check for script existence before sourcing:

```diff
+ if [ ! -f "infra/scripts/conventional-titles/parse-title.sh" ]; then
+   echo "Error: parse-title.sh script not found" >&2
+   exit 1
+ fi
+
if ! source infra/scripts/conventional-titles/parse-title.sh "$commit_title"; then
```

</blockquote></details>
<details>
<summary>infra/scripts/conventional-titles/parse-title.sh (4)</summary><blockquote>

`1-17`: **Well-documented script header!**

The header clearly explains the purpose, expected formats, outputs, and usage of the script.


Consider adding that the description must start with a capital letter, as this is validated later in the script.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: PR Title Check</summary>

[error] 1-1: PR title validation failed! Process completed with exit code 1.

</details>

</details>

---

`99-113`: **Add validation for YAML file existence**

The script directly uses the YAML file without checking if it exists first.


```diff
validate_semantics() {
  local validation_yaml="infra/config/validation.yaml"

+ # Check if validation yaml exists
+ if [ ! -f "$validation_yaml" ]; then
+   log_message "❌ Configuration file not found: $validation_yaml"
+   return 1
+ fi
+
  local keyword_exists
  keyword_exists="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .name" "$validation_yaml")"
```

---

`128-136`: **Fix display of allowed scopes**

The current code retrieves the list of scopes but doesn't format them correctly for display.


```diff
    if ! echo "$allowed_scopes" | grep -qx "$SCOPE"; then
      local allowed_scopes_csv
-      allowed_scopes_csv="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml")"
+      allowed_scopes_csv="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml" | paste -sd ',' -)"

      log_message "❌Scope '$SCOPE' is not in the allowed scopes for keyword '$KEYWORD'."
-      log_message "ℹ️Allowed scopes are:$allowed_scopes_csv"
+      log_message "ℹ️Allowed scopes are: $allowed_scopes_csv"
      return 1
    fi
```

---

`138-143`: **Add explicit success return for validate_semantics function**

The function doesn't have an explicit return statement for the success case.


```diff
  # requires_scope == "false"
  else
    if [[ -n "$SCOPE" ]]; then
      log_message "ℹ️The keyword '$KEYWORD' does not require a scope, but one was provided scope '$SCOPE'."
    fi
  fi
+  
+  # Explicitly return success
+  return 0
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 68883ba223a54e5bec338f806d4ad46934278916 and 84062a49544da5960ad538b64a52ac9bfee42949.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `.github/workflows/pr-title-check.yaml` (1 hunks)
* `infra/git/hooks/commit-msg` (1 hunks)
* `infra/scripts/conventional-titles/parse-title.sh` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: PR Title Check</summary>

<details>
<summary>infra/scripts/conventional-titles/parse-title.sh</summary>

[error] 1-1: PR title validation failed! Process completed with exit code 1.

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-23T14:43:07Z
user: login: kimseungbin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T14:50:18Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-23T14:50:37Z

Review comments:
diff_hunk: @@ -0,0 +1,148 @@
+#!/bin/bash
+
+# This script validates and parses a commit/PR title provided as an argument.
+# The title is expected to be one of the following format:
+#   - <type>(<scope>): <description>
+#   - <type>: <description>
+#
+# It extracts and sets the following environment variables:
+#  - KEYWORD:      The commit/PR type (e.e., `feat`, `fix`).
+#   - SCOPE:        The scope without surrounding parentheses (if provided).
+#   - DESCRIPTION:  The description after the colon.
+#   - MESSAGES:     A log of messages generated during validation.
+#
+# Usage:
+#   ./parse-title.sh "your title string"
+#
+
+echo "Parsing title..."
+
+# Retrieve the title from the first argument.
+TITLE="$1"
+
+# Ensure a title was provided.
+if [ -z "$TITLE" ]; then
+  echo "❌ Title not provided, Please pass a title as the first argument." >&2
+  exit 1
+fi
+
+# Global variable to collect messages.
+MESSAGES=""
+
+# Log messages by appending it to MESSAGES variable.
+log_message() {
+  MESSAGES+="$1"$'\n'
+}
+
+validate_syntax() {
+  local title="$1"
+  # Ideally, a non-capturing group would be used for the optional scope
+  # since the scope itself needs to be captured not the surrounding parentheses.
+  # However, some OSs do not support non-capturing groups in bash.
+  # As a workaround, this pattern captures the scope including its parentheses
+  # which are removed later.
+  # For environments that support non-capturing groups, you could use:
+  # pattern='^([a-z]+)(?:(\([A-Za-z0-9]+\)))?: (.+)$'
+  local pattern='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
+
+  if [[ ! "$title" =~ $pattern ]]; then
+    log_message "❌Invalid title format."
+    log_message "🔍Expected: <type>(<scope>): <description> or <type>: <description>"
+
+    # Debugging: Check where it failed.
+    if ! [[ "$title" =~ ^[a-z]+ ]]; then
+      log_message "🔍Keyword is missing or now lowercase"
+    fi
+
+    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)]]\) ]]; then
+      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
+    fi
path: infra/scripts/conventional-titles/parse-title.sh
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix regex syntax error**

There's an incorrect regex pattern with extra square brackets.


```diff
- if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)]]\) ]]; then
+ if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)\) ]]; then
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)\) ]]; then
      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
    fi
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-23T14:43:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+#!/bin/bash
+
+# This script validates and parses a commit/PR title provided as an argument.
+# The title is expected to be one of the following format:
+#   - <type>(<scope>): <description>
+#   - <type>: <description>
+#
+# It extracts and sets the following environment variables:
+#  - KEYWORD:      The commit/PR type (e.e., `feat`, `fix`).
+#   - SCOPE:        The scope without surrounding parentheses (if provided).
+#   - DESCRIPTION:  The description after the colon.
+#   - MESSAGES:     A log of messages generated during validation.
+#
+# Usage:
+#   ./parse-title.sh "your title string"
+#
+
+echo "Parsing title..."
+
+# Retrieve the title from the first argument.
+TITLE="$1"
+
+# Ensure a title was provided.
+if [ -z "$TITLE" ]; then
+  echo "❌ Title not provided, Please pass a title as the first argument." >&2
+  exit 1
+fi
+
+# Global variable to collect messages.
+MESSAGES=""
+
+# Log messages by appending it to MESSAGES variable.
+log_message() {
+  MESSAGES+="$1"$'\n'
+}
+
+validate_syntax() {
+  local title="$1"
+  # Ideally, a non-capturing group would be used for the optional scope
+  # since the scope itself needs to be captured not the surrounding parentheses.
+  # However, some OSs do not support non-capturing groups in bash.
+  # As a workaround, this pattern captures the scope including its parentheses
+  # which are removed later.
+  # For environments that support non-capturing groups, you could use:
+  # pattern='^([a-z]+)(?:(\([A-Za-z0-9]+\)))?: (.+)$'
+  local pattern='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
+
+  if [[ ! "$title" =~ $pattern ]]; then
+    log_message "❌Invalid title format."
+    log_message "🔍Expected: <type>(<scope>): <description> or <type>: <description>"
+
+    # Debugging: Check where it failed.
+    if ! [[ "$title" =~ ^[a-z]+ ]]; then
+      log_message "🔍Keyword is missing or now lowercase"
+    fi
+
+    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)]]\) ]]; then
+      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
+    fi
+
+    if ! [[ "$title" =~ :\  ]]; then
+      log_message "🔍The colon separator is missing or incorrect."
+    fi
+
+    if ! [[ "$title" =~ :\ (.+)$ ]]; then
+      log_message "🔍 Description is missing after ':'."
+    fi
+
+    return 1
+  else
+
+    KEYWORD="${BASH_REMATCH[1]}"
+
+    local raw_scope="${BASH_REMATCH[2]}"
+
+    # If raw scope is non-empty, remove surrounding parentheses.
+    if [[ -n "$raw_scope" ]]; then
+      raw_scope="${raw_scope#\(}" # Remove leading '('
+      raw_scope="${raw_scope%\)}" # Remove trailing ')'
+    fi
+
+    SCOPE="$raw_scope"
+    DESCRIPTION="${BASH_REMATCH[3]}"
+
+      if [[ ! "$DESCRIPTION" =~ ^[A-Z] ]]; then
+        log_message "🔍Description must start with a capital letter."
+        return 1
+      fi
+
+    log_message "✅Valid title format."
+    log_message "ℹ️**KEYWORD**: $KEYWORD"
+    log_message "ℹ️**SCOPE**: $SCOPE"
+    log_message "ℹ️**DESCRIPTION**: $DESCRIPTION"
+  fi
+
+  return 0
+}
+
+validate_semantics() {
+  local validation_yaml="infra/config/validation.yaml"
+
+  local keyword_exists
+  keyword_exists="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .name" "$validation_yaml")"
+
+  if [[ -z "$keyword_exists" ]]; then
+    log_message "❌ Invalid keyword: $KEYWORD"
+
+    local allowed_keywords_csv
+    allowed_keywords_csv="$(yq e ".allowed_keywords[].name" "$validation_yaml" | paste -sd ',' -)"
+    log_message "ℹ️Allowed keywords are: $allowed_keywords_csv"
+
+    return 1
+  fi
+
+  local keyword_requires_scope
+  keyword_requires_scope="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .requires_scope" "$validation_yaml")"
+
+  if [[ "$keyword_requires_scope" == "true" ]]; then
+    if [[ -z "$SCOPE" ]]; then
+      log_message "❌ The keyword '$KEYWORD' requires a scope, but none was provided."
+      return 1
+    fi
+
+    # Check allowed scopes
+    local allowed_scopes
+    allowed_scopes="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml")"
+
+    if ! echo "$allowed_scopes" | grep -qx "$SCOPE"; then
+      local allowed_scopes_csv
+      allowed_scopes_csv="$(yq e ".allowed_keywords[] | select(.name == \"$KEYWORD\") | .scopes[]" "$validation_yaml")"
+
+      log_message "❌Scope '$SCOPE' is not in the allowed scopes for keyword '$KEYWORD'."
+      log_message "ℹ️Allowed scopes are:$allowed_scopes_csv"
+      return 1
+    fi
+
+  # requires_scope == "false"
+  else
+    if [[ -n "$SCOPE" ]]; then
+      log_message "ℹ️The keyword '$KEYWORD' does not require a scope, but one was provided scope '$SCOPE'."
+    fi
+  fi
+}
+
+validate_syntax "$TITLE" || return 1
+validate_semantics || return 1
+
+return 0
path: infra/scripts/conventional-titles/parse-title.sh
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Investigate PR title validation failure**

The pipeline failures indicate the PR title validation failed. This could be due to:

1. The PR title not conforming to expected format
2. The new script having issues with validation logic

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the PR title against the validation script
PR_TITLE="infra(CI): @coderabbitai"
echo "Testing PR title: $PR_TITLE"

# First check if the YAML config exists and what it contains
if [ -f "infra/config/validation.yaml" ]; then
  echo "YAML config exists. Checking content:"
  cat infra/config/validation.yaml
  
  # Check allowed keywords
  echo -e "\nAllowed keywords:"
  yq e ".allowed_keywords[].name" infra/config/validation.yaml 2>/dev/null || echo "Failed to extract keywords"
  
  # Check if 'infra' is an allowed keyword
  echo -e "\nChecking if 'infra' is allowed:"
  yq e ".allowed_keywords[] | select(.name == \"infra\") | .name" infra/config/validation.yaml 2>/dev/null || echo "Keyword 'infra' not found"
  
  # If infra exists, check if CI is an allowed scope
  echo -e "\nChecking if 'CI' is allowed for 'infra':"
  yq e ".allowed_keywords[] | select(.name == \"infra\") | .scopes[]" infra/config/validation.yaml 2>/dev/null || echo "No scopes found for 'infra'"
else
  echo "YAML config not found at infra/config/validation.yaml"
fi

# Try running the parse-title script if it exists
if [ -f "infra/scripts/conventional-titles/parse-title.sh" ]; then
  echo -e "\nRunning parse-title.sh with PR title:"
  bash -x infra/scripts/conventional-titles/parse-title.sh "$PR_TITLE"
  echo "Exit code: $?"
else
  echo "parse-title.sh not found"
fi
```

Length of output: 5448

---



</details>

**Action Required: Fix PR Title Validation Issues**

- **Description Format:** The PR title "infra(CI): @coderabbitai" fails validation because the description does not start with a capital letter. Decide whether to relax this constraint or adjust the PR title to match the expected format.
- **Dependency Issues:** The script fails during semantic validation because the `yq` (and related `paste`) command isn’t available in the environment. Ensure these dependencies are installed or add proper checks and fallback logic.
- **Script Termination:** Errors such as "`return: can only return from a function or sourced script`" indicate that `return` is used in the main script context. Replace these with `exit` statements (or wrap the logic in a function) to properly terminate the script.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: PR Title Check</summary>

[error] 1-1: PR title validation failed! Process completed with exit code 1.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-23T14:43:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,148 @@
+#!/bin/bash
+
+# This script validates and parses a commit/PR title provided as an argument.
+# The title is expected to be one of the following format:
+#   - <type>(<scope>): <description>
+#   - <type>: <description>
+#
+# It extracts and sets the following environment variables:
+#  - KEYWORD:      The commit/PR type (e.e., `feat`, `fix`).
+#   - SCOPE:        The scope without surrounding parentheses (if provided).
+#   - DESCRIPTION:  The description after the colon.
+#   - MESSAGES:     A log of messages generated during validation.
+#
+# Usage:
+#   ./parse-title.sh "your title string"
+#
+
+echo "Parsing title..."
+
+# Retrieve the title from the first argument.
+TITLE="$1"
+
+# Ensure a title was provided.
+if [ -z "$TITLE" ]; then
+  echo "❌ Title not provided, Please pass a title as the first argument." >&2
+  exit 1
+fi
+
+# Global variable to collect messages.
+MESSAGES=""
+
+# Log messages by appending it to MESSAGES variable.
+log_message() {
+  MESSAGES+="$1"$'\n'
+}
+
+validate_syntax() {
+  local title="$1"
+  # Ideally, a non-capturing group would be used for the optional scope
+  # since the scope itself needs to be captured not the surrounding parentheses.
+  # However, some OSs do not support non-capturing groups in bash.
+  # As a workaround, this pattern captures the scope including its parentheses
+  # which are removed later.
+  # For environments that support non-capturing groups, you could use:
+  # pattern='^([a-z]+)(?:(\([A-Za-z0-9]+\)))?: (.+)$'
+  local pattern='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
+
+  if [[ ! "$title" =~ $pattern ]]; then
+    log_message "❌Invalid title format."
+    log_message "🔍Expected: <type>(<scope>): <description> or <type>: <description>"
+
+    # Debugging: Check where it failed.
+    if ! [[ "$title" =~ ^[a-z]+ ]]; then
+      log_message "🔍Keyword is missing or now lowercase"
+    fi
+
+    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)]]\) ]]; then
+      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
+    fi
path: infra/scripts/conventional-titles/parse-title.sh
user: login: kimseungbin
body: Suggest commit message
created_at: 2025-03-23T14:50:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+#!/bin/bash
+
+# This script validates and parses a commit/PR title provided as an argument.
+# The title is expected to be one of the following format:
+#   - <type>(<scope>): <description>
+#   - <type>: <description>
+#
+# It extracts and sets the following environment variables:
+#  - KEYWORD:      The commit/PR type (e.e., `feat`, `fix`).
+#   - SCOPE:        The scope without surrounding parentheses (if provided).
+#   - DESCRIPTION:  The description after the colon.
+#   - MESSAGES:     A log of messages generated during validation.
+#
+# Usage:
+#   ./parse-title.sh "your title string"
+#
+
+echo "Parsing title..."
+
+# Retrieve the title from the first argument.
+TITLE="$1"
+
+# Ensure a title was provided.
+if [ -z "$TITLE" ]; then
+  echo "❌ Title not provided, Please pass a title as the first argument." >&2
+  exit 1
+fi
+
+# Global variable to collect messages.
+MESSAGES=""
+
+# Log messages by appending it to MESSAGES variable.
+log_message() {
+  MESSAGES+="$1"$'\n'
+}
+
+validate_syntax() {
+  local title="$1"
+  # Ideally, a non-capturing group would be used for the optional scope
+  # since the scope itself needs to be captured not the surrounding parentheses.
+  # However, some OSs do not support non-capturing groups in bash.
+  # As a workaround, this pattern captures the scope including its parentheses
+  # which are removed later.
+  # For environments that support non-capturing groups, you could use:
+  # pattern='^([a-z]+)(?:(\([A-Za-z0-9]+\)))?: (.+)$'
+  local pattern='^([a-z]+)(\([A-Za-z0-9]+\))?: (.+)$'
+
+  if [[ ! "$title" =~ $pattern ]]; then
+    log_message "❌Invalid title format."
+    log_message "🔍Expected: <type>(<scope>): <description> or <type>: <description>"
+
+    # Debugging: Check where it failed.
+    if ! [[ "$title" =~ ^[a-z]+ ]]; then
+      log_message "🔍Keyword is missing or now lowercase"
+    fi
+
+    if ! [[ "$title" =~ \(([A-Za-z0-9/-]+)]]\) ]]; then
+      log_message "🔍Scope, if provided, must be enclosed in a parentheses and contain valid characters. (Optional)"
+    fi
path: infra/scripts/conventional-titles/parse-title.sh
user: login: coderabbitai[bot]
body: @kimseungbin Here's a suggested commit message:

```
fix(validation): correct regex pattern in parse-title.sh

Removes erroneous square brackets from the regex pattern that was checking scope format, 
fixing the validation logic for commit/PR titles with scopes.
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-23T14:50:37Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-23T14:38:44Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This pull request simplifies title validation for both pull requests and commit messages. The inline, regex-based validation logic in the GitHub Actions workflow and commit message hook has been replaced with calls to a new external script (`parse-title.sh`). This script validates title formats, extracts key components, logs errors, and checks allowed keywords against a YAML configuration file. Overall, the changes streamline the validation process without modifying any exported or public entities.

## Changes

| Files                                                                                          | Change Summary                                                                                                                                                           |
|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.github/.../pr-title-check.yaml`, `infra/git/hooks/commit-msg`                                | Replaced inline regex validation logic with calls to the external `parse-title.sh`, removing detailed scope and keyword checks.                                           |
| `infra/scripts/…/parse-title.sh`                                                                 | Added a new script that validates title format, extracts components (keyword, scope, description), logs errors, and verifies allowed keywords against a YAML config. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Dev as Developer
    participant WA as Workflow
    participant PS as parse-title.sh
    Dev->>WA: Create/Update Pull Request
    WA->>PS: Validate PR title
    PS-->>WA: Return success or error
    WA->>WA: Post comment with result
    WA-->>Dev: Workflow completion result
```

```mermaid
sequenceDiagram
    participant Dev as Developer
    participant CMH as Commit Hook
    participant PS as parse-title.sh
    Dev->>CMH: Commit with message
    CMH->>PS: Validate commit message title
    PS-->>CMH: Return validation result
    CMH-->>Dev: Commit allowed or error displayed
```

## Assessment against linked issues

| Objective                                                        | Addressed | Explanation |
|------------------------------------------------------------------|-----------|-------------|
| Fix scope checking logic [#24]                                   | ✅        |             |
| Separate duplicate logic into separate shell script [#24]        | ✅        |             |

## Possibly related PRs

- **docker-seminar/docker-tutorial#8**: The changes are related as both involve the simplification of validation logic for titles and commit messages by delegating the responsibility to an external script, `parse-title.sh`.
- **docker-seminar/docker-tutorial#21**: The changes are related to the retrieved PR's changes that also replace complex validation logic in a commit message hook with a call to the same external script, `parse-title.sh`.
- **docker-seminar/docker-tutorial#11**: The changes are related as both involve the implementation of an external script (`parse-title.sh`) for validating titles, although they focus on different contexts (PR title validation vs. commit message validation).

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 84062a49544da5960ad538b64a52ac9bfee42949 and 5bd55287902dce3d2d4a835bd8847edd8b71cd7b.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `infra/scripts/conventional-titles/parse-title.sh` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* infra/scripts/conventional-titles/parse-title.sh

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=docker-seminar/docker-tutorial&utm_content=25):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-23T14:38:48Z
body: > [!WARNING]
  > PR title validation failed!
  🔍Description must start with a capital letter.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-23T14:41:52Z
body: > [!NOTE]
  > PR title validation succeeded.
  ✅Valid title format.
ℹ️**KEYWORD**: infra
ℹ️**SCOPE**: CI
ℹ️**DESCRIPTION**: Simplify Title Validation for PRs and Commits Using parse-title.sh

author_association: NONE