mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
ci: add GitHub Actions workflow - LaTeX smoke compiles, skill lint, CLI typechecks, placeholder integrity (#59)
Every PR to this repo is currently verified by hand. This adds the checks
a machine can do:
- latex-smoke: compiles cv/main_example.tex (lualatex) and the new
cover_letters/cover_example.tex (xelatex) in the texlive/texlive
container, failing on any LaTeX error. Exact page-count assertions
(CV=2, cover letter=1) run on the upstream repo only
- lint (tools/lint_skills.py, also runnable locally): every SKILL.md has
parseable YAML frontmatter with name+description (frontmatter breakage
happened before - 37a0eed), allowed-tools 'bun run <path>' targets
exist, command files start with a '# /<name>' title, settings.json is
valid JSON with a permissions.allow list
- cli-typecheck: bun install + tsc --noEmit for all five portal CLIs
(matrix, fail-fast off)
- placeholder-integrity (upstream only): tracked template files still
carry their placeholder tokens, catching accidental personal-data
commits before they land
Fork-friendly by design: /setup personalizes CLAUDE.md, the skill files,
and main_example.tex in forks, so placeholder checks and exact page
counts are guarded with github.repository == upstream; compile success
and lint run everywhere. Live CLI smoke tests are deliberately excluded:
network-flaky, and linkedin-search is personal-use-only per its own ToS
warning - CI-automated requests would violate it. CLIs are typechecked
instead.
The cover letter previously had no tracked example (cover_*.tex is
gitignored), so cover_example.tex is new: a placeholder letter following
the documented 06 structure, demonstrating the correct itemize-outside-
lettercontent pattern. It doubles as the structural reference /apply
Step 2 looks for on fresh clones, which until now matched nothing. The
gitignore exception is ordered after Cover_*.tex because case-insensitive
filesystems match that pattern against cover_example.tex too.
Writing it surfaced a latent bug in the documented template itself:
06-cover-letter-templates.md's structure ends with \closing{Kind
regards,\} - but cover.cls appends its own \, and the doubled break
produces '! LaTeX Error: There's no line here to end.' on every compile
(nonstopmode swallows it, so it went unnoticed). Fixed in 06 and noted
in the example.
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# CI for the framework itself: LaTeX smoke compiles, skill/command lint,
|
||||
# CLI typechecks, and (upstream only) placeholder integrity.
|
||||
#
|
||||
# Fork-friendly by design: forks personalize CLAUDE.md, the skill files, and
|
||||
# cv/main_example.tex via /setup, so the placeholder-integrity job and the
|
||||
# exact page-count assertions run only on the upstream template repo. Compile
|
||||
# success and lint correctness are asserted everywhere.
|
||||
#
|
||||
# Deliberately NOT here: live smoke tests of the job-portal CLIs. They hit
|
||||
# real portals (network-flaky, and the linkedin-search skill is personal-use
|
||||
# only per its own ToS warning - CI-automated requests would violate that).
|
||||
# CLIs get typechecked instead; live testing stays a local, on-demand step.
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint skills, commands, settings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: pip install pyyaml
|
||||
- run: python tools/lint_skills.py
|
||||
|
||||
latex-smoke:
|
||||
name: Compile example CV and cover letter
|
||||
runs-on: ubuntu-latest
|
||||
container: texlive/texlive:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Compile CV example (lualatex)
|
||||
run: |
|
||||
cd cv
|
||||
lualatex -interaction=nonstopmode main_example.tex
|
||||
test -f main_example.pdf
|
||||
if grep -q '^!' main_example.log; then
|
||||
echo '::error::lualatex reported errors compiling cv/main_example.tex'
|
||||
grep -A3 '^!' main_example.log
|
||||
exit 1
|
||||
fi
|
||||
- name: Compile cover letter example (xelatex)
|
||||
run: |
|
||||
cd cover_letters
|
||||
xelatex -interaction=nonstopmode cover_example.tex
|
||||
test -f cover_example.pdf
|
||||
if grep -q '^!' cover_example.log; then
|
||||
echo '::error::xelatex reported errors compiling cover_letters/cover_example.tex'
|
||||
grep -A3 '^!' cover_example.log
|
||||
exit 1
|
||||
fi
|
||||
- name: Assert exact page counts (upstream template only)
|
||||
if: github.repository == 'MadsLorentzen/ai-job-search'
|
||||
run: |
|
||||
grep -q 'Output written on main_example.pdf (2 pages' cv/main_example.log \
|
||||
|| { echo '::error::cv/main_example.tex no longer compiles to exactly 2 pages'; exit 1; }
|
||||
grep -q 'Output written on cover_example.pdf (1 page' cover_letters/cover_example.log \
|
||||
|| { echo '::error::cover_letters/cover_example.tex no longer compiles to exactly 1 page'; exit 1; }
|
||||
|
||||
cli-typecheck:
|
||||
name: Typecheck ${{ matrix.tool }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tool:
|
||||
- jobbank-search
|
||||
- jobdanmark-search
|
||||
- jobindex-search
|
||||
- jobnet-search
|
||||
- linkedin-search
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
working-directory: .agents/skills/${{ matrix.tool }}/cli
|
||||
- run: bun run typecheck
|
||||
working-directory: .agents/skills/${{ matrix.tool }}/cli
|
||||
|
||||
placeholder-integrity:
|
||||
name: Placeholder integrity (upstream template only)
|
||||
if: github.repository == 'MadsLorentzen/ai-job-search'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Tracked template files must keep their placeholder tokens
|
||||
run: |
|
||||
fail=0
|
||||
check() {
|
||||
if ! grep -q "$2" "$1"; then
|
||||
echo "::error file=$1::expected placeholder token $2 - personal data may have been committed"
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
check CLAUDE.md '\[YOUR_NAME\]'
|
||||
check cv/main_example.tex '\[YOUR_NAME\]'
|
||||
check cover_letters/cover_example.tex '\[YOUR NAME\]'
|
||||
check .claude/skills/job-application-assistant/01-candidate-profile.md '<!-- SETUP'
|
||||
check .claude/skills/job-application-assistant/04-job-evaluation.md '\[YOUR_PRIMARY_SKILLS\]'
|
||||
exit $fail
|
||||
Reference in New Issue
Block a user