Compare commits
3 Commits
e214c40d4d
...
alice-hust
| Author | SHA1 | Date | |
|---|---|---|---|
| 4331b0659f | |||
| 3c72938f98 | |||
| 311f022b66 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||||
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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,7 +12,8 @@
|
|||||||
*.fls
|
*.fls
|
||||||
*.synctex.gz
|
*.synctex.gz
|
||||||
*.png
|
*.png
|
||||||
*.pdf # all PDFs are generated, so ignore them all
|
# all PDFs are generated, so ignore them all
|
||||||
|
*.pdf
|
||||||
|
|
||||||
result
|
result
|
||||||
.direnv
|
.direnv
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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