189 lines
7.5 KiB
YAML
189 lines
7.5 KiB
YAML
name: "Daily Resume Review"
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
schedule:
|
|
- cron: "15 7 * * *"
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
review:
|
|
name: "Run AI resume review and open PR"
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install nix
|
|
uses: https://github.com/DeterminateSystems/nix-installer-action@main
|
|
|
|
- name: Build resume PDF
|
|
id: build
|
|
run: |
|
|
set -euo pipefail
|
|
if nix build .#default > build.log 2>&1; then
|
|
echo "build_status=success" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "build_status=failed" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Prepare review context
|
|
id: context
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p archive/reviews
|
|
review_date="$(date +'%Y-%m-%d')"
|
|
review_path="archive/reviews/${review_date}-automated-review.md"
|
|
|
|
python - <<'PY'
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
prompt = Path('.github/prompts/resume-review.prompt.md').read_text(encoding='utf-8')
|
|
resume = Path('resume.tex').read_text(encoding='utf-8')
|
|
build_log = Path('build.log').read_text(encoding='utf-8', errors='replace') if Path('build.log').exists() else ''
|
|
|
|
payload = {
|
|
"model": os.getenv("OPENAI_MODEL", "gpt-5"),
|
|
"input": [
|
|
{
|
|
"role": "system",
|
|
"content": [
|
|
{
|
|
"type": "input_text",
|
|
"text": "You are an autonomous resume review agent. Follow the provided prompt exactly."
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "input_text",
|
|
"text": (
|
|
"Use the following prompt as your full instruction set:\n\n"
|
|
f"{prompt}\n\n"
|
|
"Build status from CI:\n"
|
|
f"- nix build result: {os.getenv('BUILD_STATUS', 'unknown')}\n\n"
|
|
"Build log:\n"
|
|
"```text\n"
|
|
f"{build_log[-15000:]}\n"
|
|
"```\n\n"
|
|
"Resume source:\n"
|
|
"```tex\n"
|
|
f"{resume}\n"
|
|
"```\n"
|
|
)
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
Path('review-request.json').write_text(json.dumps(payload), encoding='utf-8')
|
|
PY
|
|
|
|
echo "review_date=${review_date}" >> "$GITHUB_OUTPUT"
|
|
echo "review_path=${review_path}" >> "$GITHUB_OUTPUT"
|
|
|
|
env:
|
|
BUILD_STATUS: ${{ steps.build.outputs.build_status }}
|
|
|
|
- name: Run review agent
|
|
id: review
|
|
run: |
|
|
set -euo pipefail
|
|
api_base="${OPENAI_BASE_URL:-https://api.openai.com/v1}"
|
|
curl -sS "${api_base}/responses" \
|
|
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
-d @review-request.json \
|
|
> response.json
|
|
|
|
python - <<'PY'
|
|
import json
|
|
from pathlib import Path
|
|
|
|
response = json.loads(Path('response.json').read_text(encoding='utf-8'))
|
|
text = response.get('output_text', '').strip()
|
|
|
|
if not text:
|
|
chunks = []
|
|
for item in response.get('output', []):
|
|
for content in item.get('content', []):
|
|
if content.get('type') == 'output_text':
|
|
chunks.append(content.get('text', ''))
|
|
text = '\n'.join(chunks).strip()
|
|
|
|
if not text:
|
|
raise SystemExit('Agent response did not contain output text.')
|
|
|
|
review_path = Path('${{ steps.context.outputs.review_path }}')
|
|
review_path.write_text(text + '\n', encoding='utf-8')
|
|
PY
|
|
|
|
- name: Check whether review changed
|
|
id: review_guard
|
|
run: |
|
|
set -euo pipefail
|
|
current_review="${{ steps.context.outputs.review_path }}"
|
|
previous_review="$(ls -1 archive/reviews/*-automated-review.md 2>/dev/null | grep -vx "$current_review" | sort | tail -n1 || true)"
|
|
|
|
if [ -z "$previous_review" ]; then
|
|
echo "create_pr=true" >> "$GITHUB_OUTPUT"
|
|
echo "reason=no_previous_review" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
if cmp -s "$current_review" "$previous_review"; then
|
|
rm -f "$current_review"
|
|
echo "create_pr=false" >> "$GITHUB_OUTPUT"
|
|
echo "reason=review_unchanged" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "create_pr=true" >> "$GITHUB_OUTPUT"
|
|
echo "reason=review_changed" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Print guard decision
|
|
run: |
|
|
echo "create_pr=${{ steps.review_guard.outputs.create_pr }}"
|
|
echo "reason=${{ steps.review_guard.outputs.reason }}"
|
|
|
|
- name: Write PR body
|
|
if: steps.review_guard.outputs.create_pr == 'true'
|
|
run: |
|
|
cat > pr_body.md <<'EOF'
|
|
This PR contains the latest automated daily resume review.
|
|
|
|
Review file:
|
|
- ${{ steps.context.outputs.review_path }}
|
|
|
|
Generated by `.github/workflows/daily-resume-review.yml` using:
|
|
- Prompt: `.github/prompts/resume-review.prompt.md`
|
|
- Source: `resume.tex`
|
|
EOF
|
|
|
|
- name: Create Pull Request
|
|
id: create-pull-request
|
|
if: steps.review_guard.outputs.create_pr == 'true'
|
|
uses: https://nayeonie.com/ahuston-0/create-pull-request@main
|
|
with:
|
|
token: ${{ secrets.GH_TOKEN_FOR_UPDATES }}
|
|
add-paths: ${{ steps.context.outputs.review_path }}
|
|
body-path: pr_body.md
|
|
author: '"github-actions[bot]" <github-actions[bot]@users.noreply.github.com>'
|
|
title: 'automated: Daily resume review (${{ steps.context.outputs.review_date }})'
|
|
commit-message: 'automated: Daily resume review (${{ steps.context.outputs.review_date }})'
|
|
branch: automated/daily-resume-review
|
|
delete-branch: true
|
|
pr-labels: |
|
|
automated
|
|
review
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
contents: write |