mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 08:36:25 +00:00
3c7a1cfdf520404769c2f22385e1d572fa5c23f4
17
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
1ac677dd7d |
This PR fixes two genuine gaps in the framework — a documented command with no backing file, and CLI tools that were installed but never called. (#52)
* feat: add /upskill command file to wire the upskill skill into Claude Code The upskill skill and its full SKILL.md workflow already existed in .claude/skills/upskill/SKILL.md, but there was no corresponding command file in .claude/commands/. Without it, running /upskill in Claude Code had zero structured behaviour — Claude would improvise with no defined steps, mode detection, or output format. This commit adds .claude/commands/upskill.md as the thin orchestration layer that was missing: - Step 0: Parses \ to determine aggregate mode (no args, analyses all jobs in job_search_tracker.csv) vs. targeted mode (a URL is passed, analyses that single posting). Unrecognised input triggers a clarifying prompt rather than silently misbehaving. - Step 1: In aggregate mode, reads the tracker and exits early with a helpful message if it is empty, so the user is never dropped into a broken analysis with no data. - Step 2: Delegates all analysis work to the existing upskill SKILL.md (hard skill diff, LLM synthesis, heatmap, web-searched resources, study order, report save). No analysis logic is duplicated here. - Step 3: Presents a concise post-run summary — critical/high gaps, total estimated study time, and next-step suggestions (/scrape, /apply, review the saved report). Design principle: the command is intentionally a thin driver. All substantive logic lives in SKILL.md so it remains in one place and is easy to update independently of the command shell. * fix: wire CLI tools into /scrape as primary search mechanism The repo ships five Bun CLI search tools under .agents/skills/: - jobindex-search (Jobindex.dk — largest Danish board) - jobbank-search (Akademikernes Jobbank — academic/professional) - jobdanmark-search (Jobdanmark.dk — broad coverage) - jobnet-search (Jobnet.dk — government portal) - linkedin-search (LinkedIn public jobs-guest API — country-agnostic) Before this fix, none of them were ever called during /scrape. The job-scraper SKILL.md told Claude to run WebSearch for everything, meaning the CLIs were installed and documented but sat in dead-code limbo with no callers. Changes to .claude/skills/job-scraper/SKILL.md: 1. Added Bash to allowed-tools so the bun CLI commands are permitted by Claude Code's tool-permission system. Without this, any attempt to shell out would be blocked regardless of the instruction text. 2. Replaced the single WebSearch-only Step 1 with a three-part search strategy: Step 1a — bun availability check Runs \un --version\ first. If bun is not installed the skill gracefully degrades to WebSearch for all portals (Step 1c) and notes the fallback in the results output, rather than crashing. Step 1b — CLI tools as primary mechanism For each query term extracted from search-queries.md, runs all five CLIs with \--jobage 14 --limit 20 --format json\. Flags are consistent with each tool's documented contract so output is predictable. Each CLI call is independent: a non-zero exit or empty result on one portal does not abort searches on the others. Results are collected and merged before deduplication. Step 1c — WebSearch fallback Used for portals without a CLI skill (karriere.dk, jobfinder.dk, company career pages via site: searches) and as the universal fallback when bun is unavailable. This preserves backwards compatibility for users who have not installed bun yet. The net effect: /scrape now actually uses the CLI infrastructure the repo was built around. WebSearch remains available for portals outside the shipped skill set and for users on environments without bun. * fix: rework /scrape CLI wiring + drop /upskill command file Two changes addressing maintainer feedback on PR #52. --- /scrape: use portal SKILL.md as source of truth --- The previous approach hardcoded per-portal bun invocations directly in job-scraper/SKILL.md. This broke in practice: - jobbank requires --key (not --query); --query is not a valid flag - jobnet uses --search-string and region/occupation filters; passing --query silently returns the full unfiltered job firehose - --sort date and uniform --jobage 14 are not supported by all portals The fix removes all hardcoded per-portal flag examples. Instead, Step 1b now instructs the agent to: 1. Discover installed portal skills via .agents/skills/*/SKILL.md 2. Read each portal's own SKILL.md for its documented CLI interface 3. Translate search-queries.md terms into that portal's flag format 4. Use each portal's supported recency and limit flags This makes the scraper self-maintaining: new portals added via /add-portal are automatically included without any changes to this file, and the scraper can never drift from the CLIs again. The bun availability check (Step 1a) and Bash in allowed-tools are preserved - both are still needed. The WebSearch fallback (Step 1c) is preserved and cleaned up to cover: portals without a CLI skill, any portal whose CLI fails at runtime, and the bun-unavailable case. --- /upskill: drop command file --- .claude/commands/upskill.md is removed. /upskill is deliberately skill-hosted: .claude/skills/upskill/SKILL.md is the backing file and parses its own /upskill vs /upskill <URL> modes (same pattern as /scrape, which also has no command file). The command file created a second entry point that duplicated the skill's argument parsing, violating the single-source-of-truth principle established in #44 and #49. --------- Co-authored-by: rajpratham1 <your-email@example.com> |
||
|
|
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). |
||
|
|
f15b9fb65d |
Tighten pre-approved permissions and untrack settings.local.json (#27)
* fix: move scoped permissions to settings.json, drop curl, untrack settings.local.json Addresses #23: - Remove pre-approved Bash(curl:*) - no agent workflow uses curl, and a toolkit that routinely feeds untrusted job postings to the model should not ship a pre-approved exfiltration-capable command - Move shared permissions to .claude/settings.json (committed by convention) and scope them tighter: Bash(bun run:*) for the job portal CLIs, Bash(python/python3 salary_lookup.py:*) for salary lookups - Untrack .claude/settings.local.json - it was committed despite being listed in .gitignore; the file stays local for personal overrides Reported-by: @josealfonsomora Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(setup): warn existing cloners about stale settings.local.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
37a0eed6e7 | fix(skills): replace bold markdown with proper YAML Frontmatter (#16) | ||
|
|
974b29ec3a |
refactor: fold /setup_docs into /setup as third onboarding path (#9)
* refactor: fold /setup_docs into /setup as third onboarding path - Auto-detect documents/ at /setup Step 0; offer three paths (documents folder, single CV import, interview). - Inline document-driven merge logic into setup.md as Path A: read-before- write, additive vs. conflicting bucket merge with per-conflict prompts, inference labeling for behavioral and style files, STAR-stub generation instead of fabrication. - Step 3 substeps for skill files skip when Path A populated them; non- skill substeps (CLAUDE.md, cv/main_example.tex, search-queries.md) always run. Path C interview preserved verbatim. /setup --section <name> update flow preserved. - Delete .claude/commands/setup_docs.md. - Update /reset prompts to point only at /setup. Update documents/README.md to reference /setup throughout. Update README.md to drop the standalone /setup_docs section and add upskill/ to the skills tree. - Remove the trailing comma left in .claude/settings.local.json after the PR #6 revert (was technically invalid JSON). Single onboarding entry point. documents/ folder convention unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(README): finish readme alignment for the /setup fold - Update onboarding-paths tip to reflect three paths (was "Either" before /setup_docs landed in PR #6). - Add documents/ and upskill/ entries to the file tree, both relied on by the new /setup Path A and by /upskill respectively. - Add a brief "Other commands" section briefly describing /expand and /upskill so users discover them without having to read individual command files. /reset stays in the existing "Starting over" subsection, now linked from the new section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2ba441f3fb |
feat: add /setup_docs, /reset, /expand commands and /upskill skill
Adds four contributions from @Michael-Bach: - /setup_docs - document-driven profile population from a documents/ folder (CV, LinkedIn export, diplomas, references, past applications). Idempotent merge with explicit additive vs. conflicting buckets and per-conflict prompts. - /reset - typed-RESET confirmation gate for clearing profile data and/or documents folder contents. - /expand - additive competency enrichment from documents and public URLs already in the profile (GitHub repos, portfolio sites), with web-searched syllabus lookups for named courses and certifications. - /upskill - skill-gap analysis vs tracked jobs (or single URL), produces a prioritized heatmap and learning plan with year-tagged WebSearch queries. Also adds the documents/ folder convention with README and gitignore entries for personal output files. Closes #6 |
||
|
|
465c429f94 |
docs(skill): document moderncv color overrides and itemize spacing rule
Two additions to 05-cv-templates.md so the /apply workflow follows the
same patterns as cv/main_example.tex:
1. Add the three \renewcommand* lines to the documented preamble so
downstream CVs inherit the color1 overrides for firstname, lastname,
and section headings. Includes a short paragraph explaining the
reason and a note for users who prefer regular-weight firstname.
2. Add a "Spacing inside itemize lists" subsection that calls out the
inter-item \vspace anti-pattern with a wrong/right example and
explicitly preserves the two related patterns that DO render fine
(post-section spacer, inter-cventry \vspace{3pt}).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fa9c422d26 |
feat(apply): restore 02-behavioral-profile.md to reviewer reading list
Brings 02-behavioral-profile.md back into the reviewer's reading list (it was dropped in the earlier token-efficiency refactor). The reviewer now reads four files: 01 (candidate profile), 02 (behavioral profile), 03 (writing style), 04 (job evaluation). The LaTeX template files (05, 06) remain excluded, since those govern structural/LaTeX concerns the drafter already applied. The behavioral profile is there to let the reviewer cross-check whether the cover letter's voice matches the candidate's natural register - something 03-writing-style.md does not encode (03 has the universal rules like "no em-dashes, no cliches"; 02 has the per-person register like "Collaborator PI should not be given a combative solo-hero tone"). Also cross-references 02 explicitly in the Part B "Tone and style issues" category so the reviewer actually uses it during critique rather than just skimming it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
906ac1bc19 |
fix(apply): restore QC coverage dropped during earlier token-efficiency refactor
Audit of master vs the branch found three real coverage gaps in the reviewer prompt and drafter's revise step. This commit restores them without giving up the Part A (JSON edits) + Part B (narrative) structure. 1. Action-oriented reframing is restored as a Part B narrative category. The previous refactor folded reframing into Part A edit rationales, but structural weakness (e.g. "the whole opening paragraph is passive") does not fit a clean find-and-replace and needs prose critique to surface. 2. Tone and style phrasing is strengthened from "anything that conflicts with 03-writing-style.md" back to the master's proactive "check against... flag issues with tone, formality, or voice", with explicit examples (cliches, hedging, over-humility, inconsistent register). 3. Step 4 revise guidance is expanded from "Apply Part A... Apply Part B using judgment" (which could be read as a free-for-all) to an enumerated sub-list under Part B covering every category the reviewer returns: missed keywords, company angles, action-oriented reframing, tone/style. Keeps the architectural change (Part A/B split) but restores the master's explicit "apply every kind of fix" discipline. Also adds a small instruction to the reviewer: produce each Part B category even if the finding is "no issues" - silence on a category can be mistaken for skipping it. One remaining judgment call left unchanged in this commit: the reviewer no longer reads 02-behavioral-profile.md. The refactor excluded it on the reasoning that 03-writing-style.md carries the voice/tone rules. This is a conscious trade-off; if voice-matching starts slipping in practice, restoring 02 to the reviewer's reading list is a one-line fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
32e63e5143 |
feat(apply): add mandatory PDF compile-and-inspect step and relevance-weighted cutting
Adds a new Step 5 to the /apply workflow that compiles the CV (lualatex)
and cover letter (xelatex) and visually inspects the resulting PDFs before
presenting them to the user. This catches page-break failures that .tex
review cannot detect: orphaned cventry titles, cover letters spilling to
a second page, and bullet fonts not matching body text.
Also introduces relevance-weighted cutting as the rule for shrinking an
overflowing CV: score each candidate line by (a) relevance to the posting,
(b) uniqueness in the document, and (c) load-bearing role in the cover
letter, then cut the lowest-total-score line first, regardless of section.
This replaces the old static "cut oldest section first" heuristic, which
was wrong whenever a relevant older-role bullet competed with an
irrelevant recent-role bullet.
Bundled with the earlier pending token-efficiency improvements to the
/apply workflow (inline draft passing to the reviewer, scoped reviewer
file reads, single end-of-workflow verification pass).
Changes:
- .claude/commands/apply.md: new Step 5 (Compile & Inspect PDFs), renumber
old Step 5 to Step 6, update token-efficiency rules at top to reference
Step 6 for the verification checklist
- .claude/skills/job-application-assistant/05-cv-templates.md: switch
recommended compile engine from pdflatex to lualatex (pdflatex fails on
modern MiKTeX with fontawesome5 font-expansion errors), add
"Compile-and-Inspect Loop" section with needspace/enlargethispage
patterns, add "Relevance-weighted cutting" section
- .claude/skills/job-application-assistant/06-cover-letter-templates.md:
add compile command, add "Compile-and-Inspect Loop", document the
"\lettercontent{} + itemize" pitfall and the required font-matching
Raleway-Medium wrapper fix
- CLAUDE.md: add "Compiled PDF verification (MANDATORY)" checklist items
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c66d599d75 |
Initial release: AI-powered job application framework
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |