add automated review
Some checks failed
Check flake.lock / Check health of `flake.lock` (push) Successful in 8s
Check Nix flake / Perform Nix flake checks (push) Has been cancelled

This commit is contained in:
2026-03-27 16:49:38 -04:00
parent 00e5f60a56
commit b62a151b34
11 changed files with 235 additions and 22 deletions

View File

@@ -29,7 +29,7 @@ If the build produces errors, include them as a finding under **Formatting & Lay
Clean up any stale preview files, then convert the first page of the built PDF to a PNG and view it: Clean up any stale preview files, then convert the first page of the built PDF to a PNG and view it:
``` ```
rm -f resume-preview*.png rm -f resume-preview*.png || true # ignore if no previews exist
pdftoppm -r 150 -png resume.pdf resume-preview pdftoppm -r 150 -png resume.pdf resume-preview
``` ```

View File

@@ -0,0 +1,189 @@
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

BIN
archive/Alex Heifler - rs.pdf LFS Normal file

Binary file not shown.

BIN
archive/Alice_Huston_Resume.pdf LFS Normal file

Binary file not shown.

Binary file not shown.

BIN
archive/Resume.pdf LFS Normal file

Binary file not shown.

BIN
archive/alicehuston_resume.pdf LFS Normal file

Binary file not shown.

Binary file not shown.

BIN
archive/msimpkins_Resume.pdf LFS Normal file

Binary file not shown.

BIN
archive/skyedoto_resume.pdf LFS Normal file

Binary file not shown.

View File

@@ -249,26 +249,26 @@ to improve reliability in navigation}
% Certifications moved to Technical Skills & Certifications section above % Certifications moved to Technical Skills & Certifications section above
%\section{Projects} \section{Projects}
%\resumeSubHeadingListStart \resumeSubHeadingListStart
%\resumeProjectHeading{SwitchForward}{Jun. 2020 -- Aug. 2020} \resumeProjectHeading{SwitchForward}{Jun. 2020 -- Aug. 2020}
%\resumeItemListStart \resumeItemListStart
%\resumeItem{A \textbf{Python}-based Telegram bot to send stock \resumeItem{A \textbf{Python}-based Telegram bot to send stock
% updates for the Nintendo Switch during a supply shortage} updates for the Nintendo Switch during a supply shortage}
%\resumeItem{Used the Gmail API to receive and parse emails from a \resumeItem{Used the Gmail API to receive and parse emails from a
% Google Group tracking Nintendo Switch stock} Google Group tracking Nintendo Switch stock}
%\resumeItem{Sent updates to a Telegram announcements channel used by \resumeItem{Sent updates to a Telegram announcements channel used by
% \textbf{5-10} users} \textbf{5-10} users}
%\resumeItemListEnd \resumeItemListEnd
%
%\resumeProjectHeading{Autonomous Robot}{Aug. 2018 -- Dec. 2018} \resumeProjectHeading{Autonomous Robot}{Aug. 2018 -- Dec. 2018}
%\resumeItemListStart \resumeItemListStart
%\resumeItem{An \textbf{Arduino}-based robot designed to navigate \resumeItem{An \textbf{Arduino}-based robot designed to navigate
% through a maze} through a maze}
%\resumeItem{Primarily worked on pathplanning and control in a dynamic setting} \resumeItem{Primarily worked on pathplanning and control in a dynamic setting}
%\resumeItem{Implemented basic error-correction to account for drift \resumeItem{Implemented basic error-correction to account for drift
% during navigation} during navigation}
%\resumeItemListEnd \resumeItemListEnd
% Removing this project as it is not as relevant to the software % Removing this project as it is not as relevant to the software
% engineering positions I am applying for % engineering positions I am applying for
@@ -285,6 +285,6 @@ to improve reliability in navigation}
% resulting data} % resulting data}
%\resumeItemListEnd %\resumeItemListEnd
%\resumeSubHeadingListEnd \resumeSubHeadingListEnd
\end{document} \end{document}