diff --git a/.github/prompts/resume-review.prompt.md b/.github/prompts/resume-review.prompt.md index 695dfc2..65230a6 100644 --- a/.github/prompts/resume-review.prompt.md +++ b/.github/prompts/resume-review.prompt.md @@ -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 ``` diff --git a/.github/workflows/daily-resume-review.yml b/.github/workflows/daily-resume-review.yml new file mode 100644 index 0000000..63cdefb --- /dev/null +++ b/.github/workflows/daily-resume-review.yml @@ -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]" ' + 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 \ No newline at end of file diff --git a/resume.tex b/resume.tex index 608ba84..21e5fc0 100644 --- a/resume.tex +++ b/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}