diff --git a/.claude/commands/repo-maintenance.md b/.claude/commands/repo-maintenance.md index b5e46b0a..26cefcac 100644 --- a/.claude/commands/repo-maintenance.md +++ b/.claude/commands/repo-maintenance.md @@ -1,7 +1,7 @@ --- description: Comprehensive repository maintenance - run all health checks and updates allowed-tools: Read, Bash(script/repo-maintenance.sh:*), Bash(git:*), Bash(gh:*), Bash(npm:*), Bash(pnpm:*), Bash(jq:*), Skill -argument-hint: '[--mode full|quick|check-only] [--skip CATEGORY] [--create-pr]' +argument-hint: '[--mode full|quick|check-only] [--skip CATEGORY] [--create-pr] [--check-actions-pr-settings]' --- # Repository Maintenance Workflow @@ -29,6 +29,8 @@ script/repo-maintenance.sh $ARGUMENTS Repository state guard runs before updates. Archived repositories switch to `check-only` and skip PR creation. - Private repositories allow Dependency Review to be optional or skipped. +- GitHub Actions PR creation settings are checked with `script/repo-maintenance.sh --check-actions-pr-settings`. +- Automated issue and maintenance PR creation expects `default_workflow_permissions=write` and `can_approve_pull_request_reviews=true`. - Managed workflow templates are checked against `templates/workflows/` with `npm run workflow:sync:check`. - Workflow Lint coverage checks verify `.github/workflows/`, `.github/workflows/templates/`, and `templates/workflows/` are collected without static unmatched globs. diff --git a/script/repo-maintenance.sh b/script/repo-maintenance.sh index ebb2494d..dc1ffa70 100755 --- a/script/repo-maintenance.sh +++ b/script/repo-maintenance.sh @@ -12,11 +12,12 @@ MODE="full" SKIP_CATEGORIES="" CREATE_PR=false CHECK_REQUIRED_WORKFLOWS_ONLY=false +CHECK_ACTIONS_PR_SETTINGS_ONLY=false CONTEXT_DIR="${CONTEXT_DIR:-.context}" usage() { cat <<'EOF' -Usage: script/repo-maintenance.sh [--mode full|quick|check-only] [--skip CATEGORY] [--create-pr] [--check-required-workflows] +Usage: script/repo-maintenance.sh [--mode full|quick|check-only] [--skip CATEGORY] [--create-pr] [--check-required-workflows] [--check-actions-pr-settings] EOF } @@ -39,6 +40,11 @@ while [[ $# -gt 0 ]]; do MODE="check-only" shift ;; + --check-actions-pr-settings) + CHECK_ACTIONS_PR_SETTINGS_ONLY=true + MODE="check-only" + shift + ;; -h|--help) usage exit 0 @@ -173,6 +179,52 @@ check_repository_state() { fi } +check_actions_pr_creation_settings() { + local repo settings default_permissions can_create_pr settings_url issue_count=0 + + if ! command -v gh >/dev/null 2>&1; then + output::warning "GitHub Actions PR creation settings check skipped: gh not found" + return 0 + fi + if ! command -v jq >/dev/null 2>&1; then + output::warning "GitHub Actions PR creation settings check skipped: jq not found" + return 0 + fi + + repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || true)" + if [[ -z "$repo" || "$repo" == "null" ]]; then + output::warning "GitHub Actions PR creation settings check skipped: repository unavailable" + return 0 + fi + + settings="$(gh api "repos/$repo/actions/permissions/workflow" 2>/dev/null || true)" + if [[ -z "$settings" ]]; then + output::warning "GitHub Actions PR creation settings unavailable for $repo" + return 0 + fi + + default_permissions="$(echo "$settings" | jq -r '.default_workflow_permissions // empty')" + can_create_pr="$(echo "$settings" | jq -r '.can_approve_pull_request_reviews // false')" + settings_url="https://github.com/$repo/settings/actions" + + if [[ "$default_permissions" != "write" ]]; then + output::warning "GitHub Actions default workflow permissions are '$default_permissions' (expected: write)" + issue_count=$((issue_count + 1)) + fi + + if [[ "$can_create_pr" != "true" ]]; then + output::warning "GitHub Actions PR creation is disabled (expected: Allow GitHub Actions to create and approve pull requests)" + issue_count=$((issue_count + 1)) + fi + + if [[ "$issue_count" -gt 0 ]]; then + echo "Settings: $settings_url" + return 1 + fi + + output::success "GitHub Actions PR creation settings ok" +} + check_workflow_templates() { if npm run workflow:sync:check --if-present >/dev/null 2>&1; then output::success "Workflow template sync ok" @@ -334,6 +386,11 @@ if [[ "$CHECK_REQUIRED_WORKFLOWS_ONLY" == "true" ]]; then exit $? fi +if [[ "$CHECK_ACTIONS_PR_SETTINGS_ONLY" == "true" ]]; then + check_actions_pr_creation_settings + exit $? +fi + cat < { + test('documents and checks repository workflow permissions needed for automated PR creation', () => { + const command = readRepoFile('.claude/commands/repo-maintenance.md'); + const script = readRepoFile('script/repo-maintenance.sh'); + + expect(command).toContain('script/repo-maintenance.sh --check-actions-pr-settings'); + expect(command).toContain('default_workflow_permissions=write'); + expect(command).toContain('can_approve_pull_request_reviews=true'); + expect(script).toContain('check_actions_pr_creation_settings'); + expect(script).toContain('repos/$repo/actions/permissions/workflow'); + expect(script).toContain('https://github.com/$repo/settings/actions'); + }); + + test('succeeds when Actions can create pull requests', () => { + const result = runActionsPrSettingsScript({ + default_workflow_permissions: 'write', + can_approve_pull_request_reviews: true, + }); + + expect(result.status).toBe(0); + expect(result.output).toContain('GitHub Actions PR creation settings ok'); + }); + + test('warns with settings URL when Actions cannot create pull requests', () => { + const result = runActionsPrSettingsScript({ + default_workflow_permissions: 'read', + can_approve_pull_request_reviews: false, + }); + + expect(result.status).toBe(1); + expect(result.output).toContain("default workflow permissions are 'read'"); + expect(result.output).toContain('GitHub Actions PR creation is disabled'); + expect(result.output).toContain('https://github.com/owner/repo/settings/actions'); + }); +});