# 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/content assertions run only on the upstream template repo. # Compile success, lint correctness, and an extractable PDF text layer 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). # CI runs typechecks and the checked-in fixture/mock test suites only; live # portal testing stays a local, on-demand step. # # Security posture: this template ships pre-approved Claude Code permissions # and CLI code that every fork user executes, so the security-guards job # fails PRs that widen settings.json permissions, weaken the personal-data # gitignore rules, or add package lifecycle scripts; dependency-review flags # newly introduced vulnerable/malicious dependencies. Honest limit: a PR can # edit this workflow itself, so these guards catch accidents and casual # attempts, not a determined author - branch protection with required checks # and human review of workflow/settings diffs remain the real backstop. # Actions are pinned to commit SHAs; the token is read-only. name: CI on: push: branches: [master] pull_request: workflow_dispatch: permissions: contents: read jobs: lint: name: Lint skills, commands, settings runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - run: pip install pyyaml - run: python tools/lint_skills.py - name: Framework version guard (upstream template only) if: github.repository == 'MadsLorentzen/ai-job-search' run: python tools/check_framework_version.py security-guards: name: Security guards (permissions, gitignore, manifests) runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - run: python tools/security_guards.py python-tests: name: Python tool tests runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - run: python -m unittest discover -s tests -t . -v dependency-review: name: Dependency review (upstream PRs only) # Requires the repo's Dependency graph, which forks never inherit and # which may be disabled upstream - so: upstream PRs only, and the # graph is probed first. If it is unavailable, the job warns and # passes instead of hard-failing (the same graceful-skip pattern the # workflow uses for optional tools). Enabling Dependency graph under # Settings -> Advanced Security activates the real check. if: github.event_name == 'pull_request' && github.repository == 'MadsLorentzen/ai-job-search' runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Probe Dependency graph availability id: graph run: | code=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: Bearer ${{ github.token }}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${{ github.repository }}/dependency-graph/sbom") if [ "$code" = "200" ]; then echo "enabled=true" >> "$GITHUB_OUTPUT" else echo "enabled=false" >> "$GITHUB_OUTPUT" echo "::warning::Dependency graph is not enabled on this repository (HTTP $code). Dependency review was skipped - enable Dependency graph under Settings -> Advanced Security to activate this check." fi - name: Dependency review if: steps.graph.outputs.enabled == 'true' uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 with: fail-on-severity: high latex-smoke: name: Compile example CV and cover letter runs-on: ubuntu-latest container: texlive/texlive:latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Install PDF inspection tools run: | if ! command -v pdfinfo >/dev/null || ! command -v pdftotext >/dev/null; then apt-get update apt-get install -y --no-install-recommends poppler-utils fi - name: Compile CV example (lualatex) run: | cd cv lualatex -interaction=nonstopmode -halt-on-error 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 -halt-on-error 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: Verify extractable PDF text run: | python3 tools/verify_pdf.py cv/main_example.pdf --min-chars 100 python3 tools/verify_pdf.py cover_letters/cover_example.pdf --min-chars 100 - name: Assert stock PDF structure (upstream template only) if: github.repository == 'MadsLorentzen/ai-job-search' run: | python3 tools/verify_pdf.py cv/main_example.pdf \ --pages 2 \ --contains '[your.email@example.com]' \ --contains 'Professional Experience' python3 tools/verify_pdf.py cover_letters/cover_example.pdf \ --pages 1 \ --contains 'your.email@example.com' \ --contains 'Dear [Hiring Manager / Team]' cli-checks: name: CLI checks ${{ matrix.tool }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: tool: - freehire-search - jobbank-search - jobdanmark-search - jobindex-search - jobnet-search - linkedin-search steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - run: bun install working-directory: .agents/skills/${{ matrix.tool }}/cli - run: bun run typecheck working-directory: .agents/skills/${{ matrix.tool }}/cli - name: Run fixture/mock tests when present run: | if find tests -type f -name '*.test.ts' | grep -q .; then bun test else echo "No Bun tests found for ${{ matrix.tool }}; skipping" fi 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # 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 '