diff --git a/.githooks/pre-commit.d/010-branch-guard.sh b/.githooks/pre-commit.d/010-branch-guard.sh index 41584aa..9a7a3a7 100755 --- a/.githooks/pre-commit.d/010-branch-guard.sh +++ b/.githooks/pre-commit.d/010-branch-guard.sh @@ -6,6 +6,6 @@ source "$REPO_ROOT/workflow.conf" branch=$(git rev-parse --abbrev-ref HEAD) if [ "$branch" = "$MAIN_BRANCH" ]; then echo "ERROR: Direct commits to $MAIN_BRANCH are forbidden." - echo "Create an issue and work on a feature branch: issue/{issue-id}" + echo "Create a feature branch and link it to a GitHub Issue." exit 1 fi diff --git a/.github/workflows/workflow-policy.yml b/.github/workflows/workflow-policy.yml index 204b711..78a1ce4 100644 --- a/.github/workflows/workflow-policy.yml +++ b/.github/workflows/workflow-policy.yml @@ -26,6 +26,12 @@ jobs: const branch = pr.head.ref; const prBody = `${pr.title}\n\n${pr.body || ''}`; + // Reject PRs opened directly from the base branch. + if (branch === pr.base?.ref || branch === 'main') { + core.setFailed(`PR must not be opened directly from the main branch. Create a feature branch and link it to an issue (e.g. 'Closes #N' in the PR body).`); + return; + } + // Extract issue number: prefer branch name (issue/{n}), fall back to a closing keyword reference in PR text. // This supports both the VS Code path (issue/{n} branches) and the Native GitHub path // where GitHub's Copilot SWE agent always creates copilot/... branches. diff --git a/tests/workflow_policy.bats b/tests/workflow_policy.bats index c595d5c..55945b9 100644 --- a/tests/workflow_policy.bats +++ b/tests/workflow_policy.bats @@ -121,3 +121,17 @@ NODE [ "$status" -eq 0 ] [[ "$output" == *'"failed":"PR cannot be ready-for-review unless linked issue #67 is status/review."'* ]] } + +@test "workflow-policy: PR from main branch fails even with closing reference" { + run_policy '{"head":{"ref":"main"},"title":"fix: something","body":"Closes #67","draft":true}' '{"number":67,"labels":[]}' + + [ "$status" -eq 0 ] + [[ "$output" == *'"failed":"PR must not be opened directly from the main branch'* ]] +} + +@test "workflow-policy: any named feature branch with closing reference passes" { + run_policy '{"head":{"ref":"my-feature-branch"},"title":"fix: something","body":"Closes #67","draft":true}' '{"number":67,"labels":[]}' + + [ "$status" -eq 0 ] + [[ "$output" == *'"failed":null'* ]] +}