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:
|
||||
|
||||
```
|
||||
rm -f resume-preview*.png
|
||||
rm -f resume-preview*.png || true # ignore if no previews exist
|
||||
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
|
||||
*.synctex.gz
|
||||
*.png
|
||||
*.pdf # all PDFs are generated, so ignore them all
|
||||
# all PDFs are generated, so ignore them all
|
||||
*.pdf
|
||||
|
||||
result
|
||||
.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
|
||||
|
||||
%\section{Projects}
|
||||
%\resumeSubHeadingListStart
|
||||
%\resumeProjectHeading{SwitchForward}{Jun. 2020 -- Aug. 2020}
|
||||
%\resumeItemListStart
|
||||
%\resumeItem{A \textbf{Python}-based Telegram bot to send stock
|
||||
% updates for the Nintendo Switch during a supply shortage}
|
||||
%\resumeItem{Used the Gmail API to receive and parse emails from a
|
||||
% Google Group tracking Nintendo Switch stock}
|
||||
%\resumeItem{Sent updates to a Telegram announcements channel used by
|
||||
% \textbf{5-10} users}
|
||||
%\resumeItemListEnd
|
||||
%
|
||||
%\resumeProjectHeading{Autonomous Robot}{Aug. 2018 -- Dec. 2018}
|
||||
%\resumeItemListStart
|
||||
%\resumeItem{An \textbf{Arduino}-based robot designed to navigate
|
||||
% through a maze}
|
||||
%\resumeItem{Primarily worked on pathplanning and control in a dynamic setting}
|
||||
%\resumeItem{Implemented basic error-correction to account for drift
|
||||
% during navigation}
|
||||
%\resumeItemListEnd
|
||||
\section{Projects}
|
||||
\resumeSubHeadingListStart
|
||||
\resumeProjectHeading{SwitchForward}{Jun. 2020 -- Aug. 2020}
|
||||
\resumeItemListStart
|
||||
\resumeItem{A \textbf{Python}-based Telegram bot to send stock
|
||||
updates for the Nintendo Switch during a supply shortage}
|
||||
\resumeItem{Used the Gmail API to receive and parse emails from a
|
||||
Google Group tracking Nintendo Switch stock}
|
||||
\resumeItem{Sent updates to a Telegram announcements channel used by
|
||||
\textbf{5-10} users}
|
||||
\resumeItemListEnd
|
||||
|
||||
\resumeProjectHeading{Autonomous Robot}{Aug. 2018 -- Dec. 2018}
|
||||
\resumeItemListStart
|
||||
\resumeItem{An \textbf{Arduino}-based robot designed to navigate
|
||||
through a maze}
|
||||
\resumeItem{Primarily worked on pathplanning and control in a dynamic setting}
|
||||
\resumeItem{Implemented basic error-correction to account for drift
|
||||
during navigation}
|
||||
\resumeItemListEnd
|
||||
|
||||
% Removing this project as it is not as relevant to the software
|
||||
% engineering positions I am applying for
|
||||
@@ -285,6 +285,6 @@ to improve reliability in navigation}
|
||||
% resulting data}
|
||||
%\resumeItemListEnd
|
||||
|
||||
%\resumeSubHeadingListEnd
|
||||
\resumeSubHeadingListEnd
|
||||
|
||||
\end{document}
|
||||
|
||||
Reference in New Issue
Block a user