mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
429e32f7c0aa32b2f4ebcc2716307997553ca703
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
779a33aa20 |
docs: make CONTRIBUTING.md PR/issue references clickable (#91)
GitHub auto-links #N only in issue/PR comments and commit messages, not in rendered markdown files - so all 26 precedent references in CONTRIBUTING.md ((#30), (#55, #56), ...) render as plain text, and the reader can't jump to the precedent being cited. Converted every bare #N to a reference-style link ([#N] in prose, URL definitions collected at the bottom of the file). Definitions use the /issues/N form, which GitHub redirects to /pull/N when the number is a PR, so each link resolves correctly whether it points at an issue or a pull request. No prose changed; existing inline links (the Discussions URLs) untouched. |
||
|
|
e16afac7b9 |
ci: add supply-chain guards — permission allowlist, gitignore rules, manifest checks, pinned actions (#84)
* ci: add supply-chain guards - permission allowlist, gitignore rules, manifest checks, pinned actions This template's threat model is unusual: it ships pre-approved Claude Code permissions (.claude/settings.json) and CLI code that every fork user executes via those permissions. A plausible-looking PR could therefore ship risk to every forker: widen a permission to Bash(*), weaken the personal-data gitignore rules, or smuggle code execution into bun install via a lifecycle script. Nothing checked for these mechanically. New job security-guards runs tools/security_guards.py (stdlib only): - settings.json: every permissions.allow entry must be in an exact, in-repo allowlist. The guard makes permission changes loud, not impossible - a PR that intentionally widens permissions must update the allowlist in the same diff, so the widening is explicit and reviewable - .gitignore: the personal-data rules (tracker, documents/**, cv/main_*, salary data, seen_jobs) must all still be present - the mirror image of the placeholder-integrity job - .agents/**/package.json: no lifecycle scripts (preinstall/install/ postinstall/prepare/prepack) and no trustedDependencies, which would execute arbitrary code during bun install on users' machines New job dependency-review (PRs only): actions/dependency-review-action flags newly introduced vulnerable or malicious dependencies, fail-on-severity high. Workflow hardening: explicit top-level permissions: contents: read (least-privilege token), and all actions pinned to commit SHAs resolved from the same major tags already in use (checkout v4, setup-python v5, setup-bun v2), with the tag recorded in a comment. Honest limit, recorded in the workflow header: 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. Verified locally: positive run passes; injecting Bash(*) into settings.json, deleting the tracker gitignore rule, and adding a postinstall script each fail the guard with the intended message, and reverting restores a clean pass. * ci: scope dependency-review to upstream PRs - forks lack Dependency graph by default Verified on a fork: the action fails with 'Dependency review is not supported on this repository' until Dependency graph is manually enabled, and forks don't inherit it. Guarded with the same github.repository == upstream condition the other upstream-only jobs use. With the graph enabled the action passes, so the config itself is sound. * ci: probe Dependency graph before dependency-review - warn and pass when unavailable The upstream PR run showed Dependency graph is disabled on the upstream repo too (the action hard-fails: 'Dependency review is not supported on this repository'), not just on forks. Only the repo owner can enable it, so a hard red X here is friction, not signal. The job now probes the dependency-graph SBOM endpoint with the workflow token first: HTTP 200 runs the real review; anything else emits a ::warning:: naming the setting to flip (Settings -> Advanced Security -> Dependency graph) and passes. Same graceful-skip pattern the workflow uses for optional tools - the check self-activates the moment the graph is enabled, no workflow change needed. |
||
|
|
6477987608 |
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 -
|
||
|
|
75fbfc56e4 |
feat: add /interview command for stage-specific interview prep from the application archive (#58)
07-interview-prep.md holds STAR examples, tough-question patterns, questions to ask, and a roleplay protocol - and the job-application- assistant skill names interview preparation as its Step 4 - but no command operationalizes any of it. /apply ends at 'files ready', and the moment an interview is scheduled, nothing picks the thread back up. /interview does, drawing on the archive /outcome now maintains. /apply optimizes what the company reads; /interview optimizes what the company hears. The bridge is consistency: the interviewer read the submitted CV and cover letter, so prep must match what they claim. How it works: - Loads the application context from documents/applications/ <company>_<role>/: the exact posting, the submitted drafts, and outcome.md's stage + recorded feedback (stage-N feedback is the highest-priority input for stage-N+1 prep). Graceful fallbacks for applications that predate /outcome, stating plainly what is missing - Runs the Company Research Checklist that 04-job-evaluation.md defines but nothing executes, plus interviewer-angle notes from public professional info only. Every company claim is independently verified before it enters the pack - same rule as cover-letter claims - Builds a stage-specific prep pack: likely questions (earlier-stage feedback first, then fit-evaluation gaps with honest bridge answers per 07's you-don't-have-X pattern, then posting requirements, then stage type), STAR mapping via 07's Use-for tags with new drafts grounded strictly in profile facts, a consistency brief of the submitted documents' probeable claims, customized tough questions, 4-6 questions to ask, logistics tips - Saves the pack to documents/applications/<company>_<role>/ interview_prep_<stage>.md - one per stage, kept as history, gitignored with the rest of the archive - Offers a mock interview following 07's Roleplay Guidelines verbatim, with feedback calibrated against the behavioral profile (same voice-consistency rule the /apply reviewer applies) - Never edits framework or profile files, except appending user-approved STAR examples to 07 on explicit request Integration: /outcome suggests /interview when an interview stage is recorded; documents/README.md documents the interview_prep_<stage>.md files (and that /setup ignores them); README commands list and file tree. |
||
|
|
4488290274 |
feat: add /outcome command to record application results and close the calibration loop (#54)
/setup Path A already mines documents/applications/<company>_<role>/ (job_posting.md, submitted drafts, outcome.md) to calibrate 04-job-evaluation.md and surface STAR candidates - but nothing in the workflow systematically writes those folders, so the calibration machinery only runs for users who hand-maintain the archive. /outcome closes the loop: it writes the data /setup reads. How it works: - Identifies the application from job_search_tracker.csv (by argument, or by listing open applications); applications made outside the workflow get a new tracker row - Records progress updates (interview stages, offers) and resolutions using the exact status enum documents/README.md documents, plus one additive value: in_progress, for open applications between updates. /setup's calibration only draws conclusions from final statuses - Archives the submitted cv_draft.tex / cover_letter.tex (copy, never move; existing archived files are never overwritten - the archive is what was actually submitted) and fetches job_posting.md from the tracker's source URL while it is still alive; a dead URL gets a user-pasted copy or an explicit unavailable stub, never a reconstruction - Updates the tracker row's status and notes; never restructures the CSV - After 3+ resolved outcomes (or a repeating pattern), points the user back to /setup Path A - /outcome writes data, /setup interprets it, and this command never edits framework or profile files itself - Idempotent: re-running appends stages and dated notes, never duplicates folders, rows, or history Also aligns the outcome.md status enum across docs: setup.md Step A3 listed hired/rejected/no_response/interview_only while documents/README.md already had offer_declined; both now carry the full enum including in_progress. documents/applications/** and the tracker are already gitignored, so all recorded data stays personal. Docs: README (commands list, file tree), documents/README.md (/outcome cross-reference and in_progress semantics), one-line handoff at the end of /apply Step 6. |
||
|
|
fce2cf23c0 |
feat: add /rank command to triage scraped jobs into a ranked shortlist (#43)
/scrape finds and dedupes postings; /apply evaluates one at a time in depth. Nothing connects the two ends: after a scrape returns 20 jobs, the user eyeballs a table to decide where to spend /apply effort. /rank is the bridge: batch-score every new posting against the fit framework and return a ranked shortlist. How it works: - Selects jobs with status "new" from job_scraper/seen_jobs.json (--all re-ranks everything unapplied; a focus argument filters), excluding anything already in job_search_tracker.csv - Dispatches parallel general-purpose agents (~5 jobs each) that WebFetch each posting and score the five dimensions from 04-job-evaluation.md. The rubric (skill match areas, career goals, deal-breakers) is passed inline per the same token-efficiency rules /apply uses; agents score only from actually fetched content and mark dead postings expired, never guessing from a title - Triage depth by design: posting text vs. profile only - no company research, no salary lookups. /apply's Step 1 evaluation stays authoritative and always re-runs on handoff - Aggregates with the framework's 30/25/15/30 weighting and verdict bands; location deal-breakers veto regardless of score; deadlines within 7 days get urgency flags and win ties - Updates seen_jobs.json additively (status "ranked"/"expired" plus rank_score/rank_verdict/rank_date) so /scrape dedup keeps working; the tracker is read-only. Re-running is idempotent Integration: job-scraper SKILL.md documents the new status values and suggests /rank after large scrape batches; README (commands list, file tree, quick-start step 4). |
||
|
|
2551824ecb |
feat: add ATS & keyword verification pass to /apply's compile-and-inspect loop (#40)
An ATS reads the compiled PDF's embedded text layer, not the rendered page, and LaTeX can silently produce PDFs whose text extracts as garbage: icon glyphs where contact details should be, (cid:*) markers from fonts without Unicode mappings, interleaved lines from multi-column layouts. This matters more now that /add-template lets users bring arbitrary templates. The existing Step 5 loop verifies what a human sees; this adds verification of what a parser sees. New Step 5d in /apply (CV only - cover letters rarely go through keyword screening; cleanup renumbered to 5e): - Extract the CV PDF's text layer with pdftotext -layout. pdftotext (poppler) is an optional dependency: if missing, the mechanical check is skipped with a warning and keyword coverage falls back to the visual PDF read - the same graceful-skip pattern as salary_lookup.py - Parseability checks verified against a real extraction of the stock template: email/phone must survive as literal text (fontawesome icons extract as harmless glyph-name noise like MOBILE-ALT/Envelope, but a contact detail carried only by an icon or hyperlink is invisible to ATS), no (cid:*) or replacement-character garbage, reading order matching visual order, dates present - Keyword coverage reuses the required/preferred list from Step 1, matched in the posting's language, reported as covered / synonym-only / missing-have-it / missing-gap. Honesty rule enforced: keywords the profile genuinely supports get added to experience bullets; genuine gaps stay visible, never stuffed Integration: CLAUDE.md verification checklist section, ATS Parseability guidance in 05-cv-templates.md, narrow Bash(pdftotext:*) entry in the pre-approved permissions (keeping with the tightened scope from #27), cv/*.txt gitignored (extraction is personal data; also deleted by the step itself), and optional-dependency docs in README and SETUP. |
||
|
|
79b153764d |
feat: add /add-portal command for generating local job-portal search skills (#37)
The README has always invited users outside Denmark to build equivalents of
the four Danish portal CLI skills, but doing so meant reverse-engineering
.agents/skills/*/cli/ by hand. /add-portal turns that invitation into a
guided workflow:
- Interviews the user for the portal URL, skill name, market/language
(trigger phrases in the local language, like the Danish skills), and a
realistic test query
- Investigates the portal before writing code: search-URL pattern, result
structure (JSON API preferred over HTML), detail-page pattern, robots.txt
and access rules. Auth-walled portals are declined; portals with
restrictive terms get a prominent personal-use-only warning in the
generated SKILL.md (same as linkedin-search)
- Scaffolds from the canonical structure with linkedin-search as the
zero-dependency reference, enforcing the shared portal-skill contract:
search/detail commands, common flags, {meta, results} JSON shape, stderr
JSON errors, backoff on 429/5xx, chunked parsing
- Mandatory live test-run (search + detail + test suite) before registering
- Optionally wires the portal into /scrape via search-queries.md
The generator is country-agnostic; its output is market-specific and stays
in the user's fork, matching the repo policy that upstream remains a
universal template.
Docs: README (commands list, file structure, Job search tools section) and
SETUP.md (CLI install section pointer).
|
||
|
|
6b144dc456 |
feat: add /add-template command for registering custom LaTeX templates (#30)
Users could already swap the stock moderncv/cover.cls templates, but only by hand-editing the guidance in 05-cv-templates.md and 06-cover-letter-templates.md. /add-template automates that: - Interviews the user for the template's instructions: compile engine, fonts (bundled files or system), style rules to preserve, and hard page limit - Stores the template profile-agnostic ([PLACEHOLDER] tokens) under templates/ with a TEMPLATE.md manifest, so templates are safe to commit and share - Runs a mandatory test compile with dummy data before registering anything - Activates via a single managed block in 05/06, which /apply already reads, so no changes to the /apply workflow are needed; --use default is a clean revert to the stock templates - --list and --use <name> manage multiple registered templates Docs: README (commands list, file structure, LaTeX templates section) and SETUP.md (compile section pointer). |