1+ # This is a GitHub Action workflow that automates feedback for Java assignments.
2+ # It is designed to be used with GitHub Classroom.
3+ #
4+ # How it works:
5+ # 1. It triggers when a "feedback" pull request is opened or updated.
6+ # 2. It reads the assignment instructions from the README.md file.
7+ # 3. It gathers all the Java source code from the `src/main/java` directory.
8+ # 4. It gathers all the Java test code from the `src/test/java` directory.
9+ # 5. It sends the instructions, source code, and test code to an LLM on OpenRouter.
10+ # 6. The LLM generates feedback based on the provided information.
11+ # 7. The feedback is then posted as a comment on the pull request.
12+
13+ name : AI-Powered Feedback
14+
15+ on :
16+ pull_request :
17+ types : [ opened, synchronize ]
18+ branches :
19+ - feedback
20+
21+ jobs :
22+ provide_feedback :
23+ runs-on : ubuntu-latest
24+ permissions :
25+ pull-requests : write
26+ env :
27+ OPENROUTER_MODEL : ${{ secrets.OPENROUTER_MODEL }}
28+ SYSTEM_PROMPT : ${{ vars.SYSTEM_PROMPT }}
29+ steps :
30+ - name : Checkout repository
31+ uses : actions/checkout@v5
32+
33+ - name : Read assignment instructions
34+ id : instructions
35+ run : |
36+ # Reads the content of the README.md file into an output variable.
37+ # The `EOF` marker is used to handle multi-line file content.
38+ echo "instructions=$(cat README.md | sed 's/\"/\\\"/g' | sed 's/$/\\n/' | tr -d '\n' | sed 's/\\n/\\\\n/g')" >> $GITHUB_OUTPUT
39+
40+ - name : Read source code
41+ id : source_code
42+ run : |
43+ {
44+ echo 'source_code<<EOF'
45+ find src/main/java -type f -name "*.java" | while read -r file; do
46+ echo "=== File: $file ==="
47+ cat "$file"
48+ echo
49+ done
50+ echo 'EOF'
51+ } >> "$GITHUB_OUTPUT"
52+
53+ - name : Read test code
54+ id : test_code
55+ run : |
56+ {
57+ echo 'test_code<<EOF'
58+ if [ -d "src/test/java" ]; then
59+ find src/test/java -type f -name "*.java" | while read -r file; do
60+ echo "=== File: $file ==="
61+ cat "$file"
62+ echo
63+ done
64+ else
65+ echo "No test code found."
66+ fi
67+ echo 'EOF'
68+ } >> "$GITHUB_OUTPUT"
69+ - name : Generate AI Feedback
70+ id : ai_feedback
71+ run : |
72+ # This step sends the collected data to the OpenRouter API.
73+ INSTRUCTIONS=$(jq -Rs . <<'EOF'
74+ ${{ steps.instructions.outputs.instructions }}
75+ EOF
76+ )
77+ SOURCE_CODE=$(jq -Rs . <<'EOF'
78+ ${{ steps.source_code.outputs.source_code }}
79+ EOF
80+ )
81+ TEST_CODE=$(jq -Rs . <<'EOF'
82+ ${{ steps.test_code.outputs.test_code }}
83+ EOF
84+ )
85+
86+ if [ -z "$INSTRUCTIONS" ] || [ -z "$SOURCE_CODE" ] || [ -z "$TEST_CODE" ]; then
87+ echo "Error: One or more required variables are not set."
88+ exit 1
89+ fi
90+
91+ # Assigning to USER_CONTENT with variable expansion
92+ PAYLOAD="Please provide feedback on the following Java assignment.
93+
94+ --- Assignment Instructions ---
95+ ${INSTRUCTIONS}
96+
97+ --- Source files ---
98+ ${SOURCE_CODE}
99+
100+ --- Test files ---
101+ ${TEST_CODE}"
102+
103+ JSON_CONTENT=$(jq -n \
104+ --arg model "$OPENROUTER_MODEL" \
105+ --arg system_prompt "$SYSTEM_PROMPT" \
106+ --arg payload "$PAYLOAD" \
107+ '{
108+ model: $model,
109+ messages: [
110+ {role: "system", content: $system_prompt},
111+ {role: "user", content: $payload}
112+ ]
113+ }')
114+
115+ echo "$JSON_CONTENT"
116+
117+ API_RESPONSE=$(echo "$JSON_CONTENT" | curl https://openrouter.ai/api/v1/chat/completions \
118+ -H "Authorization: Bearer ${{ secrets.OPENROUTER_API_KEY }}" \
119+ -H "Content-Type: application/json" \
120+ -d @-)
121+
122+ echo "$API_RESPONSE"
123+
124+ FEEDBACK_CONTENT=$(echo "$API_RESPONSE" | jq -r '.choices[0].message.content')
125+ echo "feedback<<EOF" >> $GITHUB_OUTPUT
126+ echo "$FEEDBACK_CONTENT" >> $GITHUB_OUTPUT
127+ echo "EOF" >> $GITHUB_OUTPUT
128+
129+ - name : Post Feedback as PR Comment ✍️
130+ uses : actions/github-script@v7
131+ env :
132+ FEEDBACK_BODY : ${{ steps.ai_feedback.outputs.feedback }}
133+ with :
134+ github-token : ${{secrets.GITHUB_TOKEN}}
135+ script : |
136+ // A unique signature to identify our comment
137+ const signature = "";
138+ const body = `${process.env.FEEDBACK_BODY}\n\n${signature}`;
139+ const { owner, repo } = context.repo;
140+ const issue_number = context.issue.number;
141+
142+ // Get all comments on the pull request
143+ const { data: comments } = await github.rest.issues.listComments({
144+ owner,
145+ repo,
146+ issue_number,
147+ });
148+
149+ // Find a previous comment by the action using the signature
150+ const previousComment = comments.find(comment => comment.body.includes(signature));
151+
152+ if (previousComment) {
153+ // If a comment exists, update it
154+ await github.rest.issues.updateComment({
155+ owner,
156+ repo,
157+ comment_id: previousComment.id,
158+ body,
159+ });
160+ console.log('Updated existing feedback comment.');
161+ } else {
162+ // If no comment exists, create a new one
163+ await github.rest.issues.createComment({
164+ owner,
165+ repo,
166+ issue_number,
167+ body,
168+ });
169+ console.log('Posted new feedback comment.');
170+ }
0 commit comments