add automated review
This commit is contained in:
2
.github/prompts/resume-review.prompt.md
vendored
2
.github/prompts/resume-review.prompt.md
vendored
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
189
.github/workflows/daily-resume-review.yml
vendored
Normal file
189
.github/workflows/daily-resume-review.yml
vendored
Normal 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
BIN
archive/Alex Heifler - rs.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/Alice_Huston_Resume.pdf
LFS
Normal file
BIN
archive/Alice_Huston_Resume.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/James_Leeson_Resume_Current_3.15.pdf
LFS
Normal file
BIN
archive/James_Leeson_Resume_Current_3.15.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/Resume.pdf
LFS
Normal file
BIN
archive/Resume.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/alicehuston_resume.pdf
LFS
Normal file
BIN
archive/alicehuston_resume.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/juliamartin_resume2020.pdf
LFS
Normal file
BIN
archive/juliamartin_resume2020.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/msimpkins_Resume.pdf
LFS
Normal file
BIN
archive/msimpkins_Resume.pdf
LFS
Normal file
Binary file not shown.
BIN
archive/skyedoto_resume.pdf
LFS
Normal file
BIN
archive/skyedoto_resume.pdf
LFS
Normal file
Binary file not shown.
42
resume.tex
42
resume.tex
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user