mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
* ci: run the Python test suite - CONTRIBUTING.md asks for tests CI never executes
CONTRIBUTING.md tells contributors to put Python tool tests in tests/
and run the relevant suites, and tests/ now holds real ones
(test_salary_lookup.py, test_convert_salary_excel.py from #75) - but no
CI job executes them. A suite that never runs in CI can't gate a PR and
silently rots. New python-tests job: unittest discover over tests/,
stdlib only, no new dependencies. Future test files run without any
workflow change.
Also lands tests/test_security_guards.py, which missed #84's merge
window (pushed to the branch as #84 was being merged; the merge took
2a6cb8c, the tests were 260c37a). 13 unittest cases in the existing
tests/ style: each copies the guard script into a synthetic repo tree
and runs it as a subprocess - the same way CI invokes it - asserting
real exit codes and messages. Every forbidden state fails (Bash(*) and
Bash(curl:*) additions, each personal-data gitignore rule removed one
at a time, each forbidden lifecycle script, trustedDependencies,
invalid settings JSON, zero manifests); every non-event passes (dropped
shipped permission, extra ignore rules, benign scripts, hostile
manifest inside node_modules); and the real repo passes its own guards.
22 tests total, all passing locally via the exact command the job runs.
* test: use benign lifecycle-script values in fixtures - AV heuristics flag attack-shaped strings
Review found the curl-pipe-to-sh fixture value matches a real Defender
signature (Trojan:Script/Stealer.HAX!MTB): Windows quarantines the temp
package.json mid-test, making the suite flaky for any Windows
contributor who runs it - while proving nothing extra, since the guard
flags the script KEY and never inspects the value.
Fixture values are now 'echo test' (also in the node_modules-ignored
test, same class of string), with a comment on the key-only test
explaining why the value must stay benign so a future 'make the fixture
realistic' cleanup doesn't reintroduce the quarantine flake. Coverage
is unchanged: same keys, same assertions, 13 tests passing.
174 lines
7.3 KiB
YAML
174 lines
7.3 KiB
YAML
# 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.
|
|
#
|
|
# 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
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: "3.12"
|
|
- run: pip install pyyaml
|
|
- run: python tools/lint_skills.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: 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:
|
|
- 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
|
|
|
|
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 '<!-- SETUP'
|
|
check .claude/skills/job-application-assistant/04-job-evaluation.md '\[YOUR_PRIMARY_SKILLS\]'
|
|
exit $fail
|