Skip to content

Commit bfb6f0e

Browse files
sylvansysclaude
andauthored
feat: Add workflow template for external repos (#62)
* feat: Add workflow template for external repos External repos like nodes-md need to manually add the Constellos review workflow since automatic provisioning isn't working. This adds: - templates/constellos-review.yml: Ready-to-use workflow for external repos that references constellos/constellos-actions actions via @main - Updated CLAUDE.md with Manual Setup instructions Closes #61 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: Config-driven GitHub agent review system - Rename repo from constellos-actions to github-agents - Create reusable workflow at .github/workflows/review.yml - Simplify root action.yml to generic agent runner - Add context-reviewer action (stub) - Add create-check-run action - Update CLAUDE.md with new architecture docs - Remove templates/ (replaced by reusable workflow) - Update .github/templates/constellos-review.yml External repos can now call the reusable workflow: uses: constellos/github-agents/.github/workflows/review.yml@main Closes #61 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 41ae675 commit bfb6f0e

6 files changed

Lines changed: 643 additions & 147 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: Context Reviewer
2+
description: AI-powered PR context review - analyzes PR for completeness and clarity (stub)
3+
4+
branding:
5+
icon: 'file-text'
6+
color: 'blue'
7+
8+
inputs:
9+
claude_code_oauth_token:
10+
description: 'Claude Code OAuth token'
11+
required: true
12+
pr_number:
13+
description: 'Pull request number'
14+
required: true
15+
github_token:
16+
description: 'GitHub token for API access'
17+
required: true
18+
19+
outputs:
20+
passed:
21+
description: 'Whether the review passed (all checks passed)'
22+
value: ${{ steps.result.outputs.passed }}
23+
skipped:
24+
description: 'Whether the review was skipped'
25+
value: ${{ steps.result.outputs.skipped }}
26+
result:
27+
description: 'JSON result from the review'
28+
value: ${{ steps.result.outputs.result }}
29+
checks_passed:
30+
description: 'Number of checks that passed'
31+
value: ${{ steps.result.outputs.checks_passed }}
32+
checks_failed:
33+
description: 'Number of checks that failed'
34+
value: ${{ steps.result.outputs.checks_failed }}
35+
checks_skipped:
36+
description: 'Number of checks that were skipped'
37+
value: ${{ steps.result.outputs.checks_skipped }}
38+
39+
runs:
40+
using: composite
41+
steps:
42+
- name: Get context
43+
id: context
44+
shell: bash
45+
env:
46+
GITHUB_TOKEN: ${{ inputs.github_token }}
47+
run: |
48+
mkdir -p .claude/review-context
49+
50+
# Get changed files
51+
gh pr view ${{ inputs.pr_number }} --json files --jq '.files[].path' > .claude/review-context/changed.txt 2>/dev/null || touch .claude/review-context/changed.txt
52+
53+
# Get PR details
54+
gh pr view ${{ inputs.pr_number }} --json title,body,author > .claude/review-context/pr.json 2>/dev/null || echo '{}' > .claude/review-context/pr.json
55+
56+
# Check if we have context
57+
if [ ! -s ".claude/review-context/changed.txt" ]; then
58+
echo "skip=true" >> $GITHUB_OUTPUT
59+
echo "skip_reason=No changed files found" >> $GITHUB_OUTPUT
60+
else
61+
echo "skip=false" >> $GITHUB_OUTPUT
62+
fi
63+
64+
- name: Load agent prompt
65+
id: agent
66+
if: steps.context.outputs.skip != 'true'
67+
shell: bash
68+
run: |
69+
if [ -f ".constellos/agents/context.md" ]; then
70+
PROMPT=$(cat .constellos/agents/context.md)
71+
else
72+
PROMPT="# Context Review
73+
74+
Review the PR for context completeness and clarity.
75+
- Changed files: .claude/review-context/changed.txt
76+
- PR details: .claude/review-context/pr.json
77+
78+
Evaluate:
79+
1. PR title is descriptive
80+
2. PR description explains the changes
81+
3. Changes are logically grouped
82+
83+
Output json with checks array."
84+
fi
85+
86+
echo "prompt<<ENDOFPROMPT" >> $GITHUB_OUTPUT
87+
echo "$PROMPT" >> $GITHUB_OUTPUT
88+
echo "ENDOFPROMPT" >> $GITHUB_OUTPUT
89+
90+
- name: Run review
91+
id: review
92+
if: steps.context.outputs.skip != 'true'
93+
uses: anthropics/claude-code-base-action@main
94+
with:
95+
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
96+
prompt: ${{ steps.agent.outputs.prompt }}
97+
98+
- name: Extract result
99+
id: result
100+
shell: bash
101+
run: |
102+
# Use shared extraction logic
103+
source "${{ github.action_path }}/../shared/extract-result.sh"
104+
105+
# Handle skipped case
106+
if [ "${{ steps.context.outputs.skip }}" = "true" ]; then
107+
generate_skipped_result "${{ steps.context.outputs.skip_reason }}" \
108+
"Title" "Description" "Grouping"
109+
echo "passed=$PASSED" >> $GITHUB_OUTPUT
110+
echo "skipped=true" >> $GITHUB_OUTPUT
111+
echo "result=$RESULT" >> $GITHUB_OUTPUT
112+
echo "checks_passed=$CHECKS_PASSED" >> $GITHUB_OUTPUT
113+
echo "checks_failed=$CHECKS_FAILED" >> $GITHUB_OUTPUT
114+
echo "checks_skipped=$CHECKS_SKIPPED" >> $GITHUB_OUTPUT
115+
exit 0
116+
fi
117+
118+
extract_review_result "${{ steps.review.outputs.execution_file }}"
119+
120+
echo "passed=$PASSED" >> $GITHUB_OUTPUT
121+
echo "skipped=false" >> $GITHUB_OUTPUT
122+
echo "result=$RESULT" >> $GITHUB_OUTPUT
123+
echo "checks_passed=$CHECKS_PASSED" >> $GITHUB_OUTPUT
124+
echo "checks_failed=$CHECKS_FAILED" >> $GITHUB_OUTPUT
125+
echo "checks_skipped=$CHECKS_SKIPPED" >> $GITHUB_OUTPUT
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: Create Check Run
2+
description: Creates or updates a GitHub check run for agent results
3+
4+
branding:
5+
icon: 'check-square'
6+
color: 'green'
7+
8+
inputs:
9+
name:
10+
description: 'Name of the check run (e.g., "Requirements Review")'
11+
required: true
12+
sha:
13+
description: 'Commit SHA to attach the check to'
14+
required: true
15+
status:
16+
description: 'Check status: queued, in_progress, or completed'
17+
required: false
18+
default: 'completed'
19+
conclusion:
20+
description: 'Conclusion when completed: success, failure, neutral, cancelled, skipped, timed_out, action_required'
21+
required: false
22+
default: 'neutral'
23+
title:
24+
description: 'Title for the check run output'
25+
required: false
26+
default: ''
27+
summary:
28+
description: 'Summary markdown for the check run output'
29+
required: false
30+
default: ''
31+
details_url:
32+
description: 'URL for more details'
33+
required: false
34+
default: ''
35+
github_token:
36+
description: 'GitHub token with checks:write permission'
37+
required: true
38+
check_run_id:
39+
description: 'Existing check run ID to update (optional - creates new if not provided)'
40+
required: false
41+
default: ''
42+
43+
outputs:
44+
check_run_id:
45+
description: 'ID of the created/updated check run'
46+
value: ${{ steps.check.outputs.check_run_id }}
47+
check_run_url:
48+
description: 'URL of the check run'
49+
value: ${{ steps.check.outputs.check_run_url }}
50+
51+
runs:
52+
using: composite
53+
steps:
54+
- name: Create or update check run
55+
id: check
56+
shell: bash
57+
env:
58+
GITHUB_TOKEN: ${{ inputs.github_token }}
59+
run: |
60+
NAME="${{ inputs.name }}"
61+
SHA="${{ inputs.sha }}"
62+
STATUS="${{ inputs.status }}"
63+
CONCLUSION="${{ inputs.conclusion }}"
64+
TITLE="${{ inputs.title }}"
65+
SUMMARY="${{ inputs.summary }}"
66+
DETAILS_URL="${{ inputs.details_url }}"
67+
CHECK_RUN_ID="${{ inputs.check_run_id }}"
68+
69+
# Build the output object if title/summary provided
70+
OUTPUT_JSON=""
71+
if [ -n "$TITLE" ] || [ -n "$SUMMARY" ]; then
72+
OUTPUT_JSON=$(jq -n \
73+
--arg title "${TITLE:-$NAME}" \
74+
--arg summary "${SUMMARY:-No summary provided}" \
75+
'{title: $title, summary: $summary}')
76+
fi
77+
78+
if [ -n "$CHECK_RUN_ID" ]; then
79+
# Update existing check run
80+
API_PATH="repos/${{ github.repository }}/check-runs/${CHECK_RUN_ID}"
81+
METHOD="PATCH"
82+
83+
if [ "$STATUS" = "completed" ]; then
84+
BODY=$(jq -n \
85+
--arg status "$STATUS" \
86+
--arg conclusion "$CONCLUSION" \
87+
--argjson output "${OUTPUT_JSON:-null}" \
88+
'{status: $status, conclusion: $conclusion} + (if $output then {output: $output} else {} end)')
89+
else
90+
BODY=$(jq -n \
91+
--arg status "$STATUS" \
92+
--argjson output "${OUTPUT_JSON:-null}" \
93+
'{status: $status} + (if $output then {output: $output} else {} end)')
94+
fi
95+
else
96+
# Create new check run
97+
API_PATH="repos/${{ github.repository }}/check-runs"
98+
METHOD="POST"
99+
100+
if [ "$STATUS" = "completed" ]; then
101+
BODY=$(jq -n \
102+
--arg name "$NAME" \
103+
--arg head_sha "$SHA" \
104+
--arg status "$STATUS" \
105+
--arg conclusion "$CONCLUSION" \
106+
--arg details_url "$DETAILS_URL" \
107+
--argjson output "${OUTPUT_JSON:-null}" \
108+
'{name: $name, head_sha: $head_sha, status: $status, conclusion: $conclusion} + (if $details_url != "" then {details_url: $details_url} else {} end) + (if $output then {output: $output} else {} end)')
109+
else
110+
BODY=$(jq -n \
111+
--arg name "$NAME" \
112+
--arg head_sha "$SHA" \
113+
--arg status "$STATUS" \
114+
--arg details_url "$DETAILS_URL" \
115+
--argjson output "${OUTPUT_JSON:-null}" \
116+
'{name: $name, head_sha: $head_sha, status: $status} + (if $details_url != "" then {details_url: $details_url} else {} end) + (if $output then {output: $output} else {} end)')
117+
fi
118+
fi
119+
120+
echo "Request body:"
121+
echo "$BODY" | jq .
122+
123+
RESPONSE=$(gh api -X "$METHOD" "$API_PATH" --input - <<< "$BODY")
124+
125+
CHECK_ID=$(echo "$RESPONSE" | jq -r '.id')
126+
CHECK_URL=$(echo "$RESPONSE" | jq -r '.html_url')
127+
128+
echo "check_run_id=$CHECK_ID" >> $GITHUB_OUTPUT
129+
echo "check_run_url=$CHECK_URL" >> $GITHUB_OUTPUT
130+
131+
echo "Check run created/updated: $CHECK_URL"

.github/templates/constellos-review.yml

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,11 @@ on:
1111
types: [opened, synchronize, reopened]
1212

1313
jobs:
14-
# Read config and determine which agents to run
15-
config:
16-
runs-on: ubuntu-latest
17-
outputs:
18-
agents: ${{ steps.read.outputs.agents }}
19-
steps:
20-
- uses: actions/checkout@v4
21-
- id: read
22-
run: |
23-
if [ -f ".constellos/config.json" ]; then
24-
AGENTS=$(jq -c '[.agents | to_entries[] | select(.value.enabled) | .key]' .constellos/config.json)
25-
else
26-
# Default: run both agents
27-
AGENTS='["requirements","code-quality"]'
28-
fi
29-
echo "agents=$AGENTS" >> $GITHUB_OUTPUT
30-
31-
# Run each enabled agent in parallel
3214
review:
33-
needs: config
34-
runs-on: ubuntu-latest
35-
strategy:
36-
fail-fast: false
37-
matrix:
38-
agent: ${{ fromJson(needs.config.outputs.agents) }}
39-
steps:
40-
- uses: actions/checkout@v4
41-
42-
- uses: constellos/constellos-actions@main
43-
with:
44-
review_type: ${{ matrix.agent }}
45-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
46-
pr_number: ${{ github.event.number }}
47-
branch: ${{ github.head_ref }}
15+
uses: constellos/github-agents/.github/workflows/review.yml@main
16+
with:
17+
pr_number: ${{ github.event.number }}
18+
sha: ${{ github.event.pull_request.head.sha }}
19+
branch: ${{ github.head_ref }}
20+
secrets:
21+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

0 commit comments

Comments
 (0)